Skip to content

Features/support overprinting #62

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Update="SixLabors.ImageSharp" Version="3.0.*" />
<PackageReference Update="SixLabors.ImageSharp" Version="3.1.*" />
</ItemGroup>

<!-- Disable auto imports/usings from ImageSharp -->
Expand Down
4 changes: 4 additions & 0 deletions samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ public static void Main(string[] args)

page.Content.AddShapes(trimBox, static (trim, context) =>
{
context.SetExtendedGraphicsState(new ExtendedGraphicsState()
{
Overprint = true
});
context.SetStroke(new SpotColor(new Separation(LowLevel.PdfName.Get("CutContour"), PredefinedColors.Magenta), 1));
context.Rectangle(trim);
context.Stroke();
Expand Down
23 changes: 23 additions & 0 deletions src/Synercoding.FileFormats.Pdf/ExtendedGraphicsState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Synercoding.FileFormats.Pdf;

/// <summary>
/// Class representing an ExtGState dictionary.
/// </summary>
public sealed record class ExtendedGraphicsState
{
/// <summary>
/// A flag specifying whether to apply overprint.
/// There are two separate overprint parameters: one for stroking and one for all other painting operations.
/// Specifying an <see cref="Overprint"/> entry sets both parameters
/// unless there is also an <see cref="OverprintNonStroking"/> entry in the same graphics state parameter dictionary,
/// in which case the <see cref="Overprint"/> entry sets only the overprint parameter for stroking.
/// </summary>
public bool? Overprint { get; set; }

/// <summary>
/// A flag specifying whether to apply overprint for painting operations other than stroking.
/// If this entry is absent, the <see cref="Overprint"/> entry, if any, sets this parameter.
/// </summary>
public bool? OverprintNonStroking { get; set; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ namespace Synercoding.FileFormats.Pdf;
/// <summary>
/// Class representing the grahpic state of a PDF at a certain moment in time.
/// </summary>
public sealed class GraphicState
public sealed class GraphicsState
{
internal GraphicState()
internal GraphicsState()
{
CTM = Matrix.Identity;
Fill = PredefinedColors.Black;
Expand Down Expand Up @@ -111,9 +111,9 @@ internal GraphicState()
/// </summary>
public double TextRise { get; internal set; }

internal GraphicState Clone()
internal GraphicsState Clone()
{
return new GraphicState()
return new GraphicsState()
{
CTM = CTM,
Fill = Fill,
Expand Down
11 changes: 9 additions & 2 deletions src/Synercoding.FileFormats.Pdf/IContentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public interface IContentContext<TSelf>
/// <summary>
/// Represents the current graphic state
/// </summary>
GraphicState GraphicState { get; }
GraphicsState GraphicState { get; }

/// <summary>
/// Wrap the <paramref name="contentOperations"/> in save and restore state operators
Expand All @@ -40,7 +40,7 @@ public interface IContentContext<TSelf>
Task<TSelf> WrapInStateAsync<T>(T data, Func<T, TSelf, Task> contentOperations);

/// <summary>
/// Concatenate a matrix to <see cref="GraphicState.CTM"/>
/// Concatenate a matrix to <see cref="GraphicsState.CTM"/>
/// </summary>
/// <param name="matrix">The matrix to concat</param>
/// <returns>This <see cref="IContentContext{TSelf}"/> to enable chaining operations</returns>
Expand Down Expand Up @@ -94,5 +94,12 @@ public interface IContentContext<TSelf>
/// <param name="dashPattern">The dash pattern to set</param>
/// <returns>This <see cref="IContentContext{TSelf}"/> to enable chaining operations</returns>
TSelf SetDashPattern(Dash dashPattern);

/// <summary>
/// Set an extended graphics state (ExtGState) dictionary.
/// </summary>
/// <param name="extendedGraphicsState">The state to apply.</param>
/// <returns>This <see cref="IContentContext{TSelf}"/> to enable chaining operations</returns>
TSelf SetExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState);
}

2 changes: 1 addition & 1 deletion src/Synercoding.FileFormats.Pdf/ITextContentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public interface ITextContentContext : IContentContext<ITextContentContext>
ITextContentContext ShowTextOnNextLine(string text);

/// <summary>
/// Operation to show text on the next line and setting the <see cref="GraphicState.WordSpacing"/> and <see cref="GraphicState.CharacterSpacing"/>
/// Operation to show text on the next line and setting the <see cref="GraphicsState.WordSpacing"/> and <see cref="GraphicsState.CharacterSpacing"/>
/// </summary>
/// <param name="text">The text to show</param>
/// <param name="wordSpacing">The word spacing to set</param>
Expand Down
11 changes: 9 additions & 2 deletions src/Synercoding.FileFormats.Pdf/Internals/PageContentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ namespace Synercoding.FileFormats.Pdf.Internals;

internal class PageContentContext : IPageContentContext
{
public PageContentContext(ContentStream contentStream, GraphicState graphicState)
public PageContentContext(ContentStream contentStream, GraphicsState graphicState)
{
RawContentStream = contentStream;
GraphicState = graphicState;
}

public ContentStream RawContentStream { get; }

public GraphicState GraphicState { get; }
public GraphicsState GraphicState { get; }

public IPageContentContext AddImage(Image image)
{
Expand Down Expand Up @@ -155,4 +155,11 @@ public async Task<IPageContentContext> AddShapesAsync<T>(T data, Func<T, IShapeC

return this;
}

public IPageContentContext SetExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState)
{
RawContentStream.SetExtendedGraphicsState(extendedGraphicsState);

return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ namespace Synercoding.FileFormats.Pdf.Internals;

internal class ShapesContentContext : IShapeContentContext
{
public ShapesContentContext(ContentStream contentStream, GraphicState graphicState)
public ShapesContentContext(ContentStream contentStream, GraphicsState graphicState)
{
RawContentStream = contentStream;
GraphicState = graphicState;
}

public ContentStream RawContentStream { get; }

public GraphicState GraphicState { get; }
public GraphicsState GraphicState { get; }

public IShapeContentContext ConcatenateMatrix(Matrix matrix)
{
Expand Down Expand Up @@ -204,4 +204,11 @@ public IShapeContentContext EndPathNoStrokeNoFill()

return this;
}

public IShapeContentContext SetExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState)
{
RawContentStream.SetExtendedGraphicsState(extendedGraphicsState);

return this;
}
}
11 changes: 9 additions & 2 deletions src/Synercoding.FileFormats.Pdf/Internals/TextContentContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ namespace Synercoding.FileFormats.Pdf.Internals;

internal class TextContentContext : ITextContentContext
{
public TextContentContext(ContentStream contentStream, GraphicState graphicState)
public TextContentContext(ContentStream contentStream, GraphicsState graphicState)
{
RawContentStream = contentStream;
GraphicState = graphicState;
}

public ContentStream RawContentStream { get; }

public GraphicState GraphicState { get; }
public GraphicsState GraphicState { get; }

public ITextContentContext ConcatenateMatrix(Matrix matrix)
{
Expand Down Expand Up @@ -225,4 +225,11 @@ public ITextContentContext ShowTextOnNextLine(string text, double wordSpacing, d

return this;
}

public ITextContentContext SetExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState)
{
RawContentStream.SetExtendedGraphicsState(extendedGraphicsState);

return this;
}
}
14 changes: 14 additions & 0 deletions src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,20 @@ public ContentStream Paint(PdfName resource)
return this;
}

/// <summary>
/// Set an extended graphics state (ExtGState) dictionary using a gs operator..
/// </summary>
/// <param name="state">The state to apply.</param>
/// <returns>The <see cref="ContentStream"/> to support chaining operations.</returns>
public ContentStream SetExtendedGraphicsState(ExtendedGraphicsState state)
{
var name = Resources.AddExtendedGraphicsState(state);

InnerStream.Write(name).Space().Write("gs").NewLine();

return this;
}

/// <summary>
/// Write the operator (m) to the stream
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public enum LineJoinStyle
/// The outer edges of the strokes for the two segments shall be extended until they meet at an angle.
/// </summary>
/// <remarks>
/// If the segments meet at too sharp an angle (see <see cref="GraphicState.MiterLimit"/>), a bevel join shall be used instead.
/// If the segments meet at too sharp an angle (see <see cref="GraphicsState.MiterLimit"/>), a bevel join shall be used instead.
/// </remarks>
MiterJoin = 0,
/// <summary>
Expand Down
19 changes: 19 additions & 0 deletions src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ internal sealed class PageResources : IDisposable
{
private const string PREFIX_IMAGE = "Im";
private const string PREFIX_SEPARATION = "Sep";
private const string PREFIX_EXTGSTATE = "ExGs";

private readonly TableBuilder _tableBuilder;
private readonly Map<PdfName, Image> _images;
private readonly Dictionary<Separation, (PdfName Name, PdfReference Id)> _separations;
private readonly Dictionary<Type1StandardFont, PdfReference> _standardFonts;
private readonly Dictionary<ExtendedGraphicsState, (PdfName Name, PdfReference Id)> _extendedGraphicsStates;

private int _stateCounter = 0;
private int _separationCounter = 0;
private int _imageCounter = 0;

Expand All @@ -24,11 +27,15 @@ internal PageResources(TableBuilder tableBuilder)
_images = new Map<PdfName, Image>();
_separations = new Dictionary<Separation, (PdfName Name, PdfReference Id)>();
_standardFonts = new Dictionary<Type1StandardFont, PdfReference>();
_extendedGraphicsStates = new Dictionary<ExtendedGraphicsState, (PdfName Name, PdfReference Id)>();
}

public IReadOnlyDictionary<PdfName, Image> Images
=> _images.Forward;

public IReadOnlyDictionary<ExtendedGraphicsState, (PdfName Name, PdfReference Id)> ExtendedGraphicsStates
=> _extendedGraphicsStates;

internal IReadOnlyDictionary<Separation, (PdfName Name, PdfReference Id)> SeparationReferences
=> _separations;

Expand Down Expand Up @@ -105,4 +112,16 @@ internal PdfName AddSeparation(Separation separation)

return name;
}

internal PdfName AddExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState)
{
if (_extendedGraphicsStates.TryGetValue(extendedGraphicsState, out var tuple))
return tuple.Name;

var key = PREFIX_EXTGSTATE + Interlocked.Increment(ref _stateCounter).ToString().PadLeft(6, '0');
var name = PdfName.Get(key);
_extendedGraphicsStates[extendedGraphicsState] = (name, _tableBuilder.ReserveId());

return name;
}
}
28 changes: 28 additions & 0 deletions src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ public ObjectStream Write(PdfPage page)
}
}));
}

if (resources.ExtendedGraphicsStates.Count != 0)
{
stream.Write(PdfName.Get("ExtGState"), resources.ExtendedGraphicsStates.Values, static (extGStates, stream) => stream.Dictionary(extGStates, static (extendedGStates, dict) =>
{
foreach (var (name, reference) in extendedGStates)
{
dict.Write(name, reference);
}
}));
}

}));

// Content stream
Expand Down Expand Up @@ -217,6 +229,22 @@ public ObjectStream Write(PdfReference reference, Separation separation)
return this;
}

public ObjectStream Write(PdfReference reference, ExtendedGraphicsState state)
{
if (!_tableBuilder.TrySetPosition(reference, InnerStream.Position))
return this;

_indirectDictionary(reference, state, static (state, dict) =>
{
if (state.Overprint.HasValue)
dict.Write(PdfName.Get("OP"), state.Overprint.Value);
if (state.OverprintNonStroking.HasValue)
dict.Write(PdfName.Get("op"), state.OverprintNonStroking.Value);
});

return this;
}

private void _indirectDictionary<T>(PdfReference reference, T data, Action<T, PdfDictionary> dictionaryAction)
{
InnerStream
Expand Down
16 changes: 16 additions & 0 deletions src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ public PdfDictionary Write(PdfName key, int value)
return this;
}

/// <summary>
/// Write a boolean to the dictionary
/// </summary>
/// <param name="key">The key of the item in the dictionary</param>
/// <param name="value">The boolean to write</param>
/// <returns>The <see cref="PdfDictionary"/> to support chaining operations.</returns>
public PdfDictionary Write(PdfName key, bool value)
{
_stream
.Write(key)
.Space()
.Write(value);

return this;
}

/// <summary>
/// Write a text to the dictionary
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ public PdfStream Write(char c)
return WriteByte((byte)( c & 0xFF ));
}

/// <summary>
/// Write a <see cref="bool"/> to the stream
/// </summary>
/// <param name="b">The boolean to write.</param>
/// <returns>The calling <see cref="PdfStream"/> to support chaining operations.</returns>
public PdfStream Write(bool b)
{
return b
? Write("true")
: Write("false");
}

/// <summary>
/// Write a <see cref="int"/> to the stream
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Synercoding.FileFormats.Pdf/PdfPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal PdfPage(TableBuilder tableBuilder, PageTree parent)
Resources = new PageResources(_tableBuilder);
var contentStream = new ContentStream(tableBuilder.ReserveId(), Resources);

Content = new PageContentContext(contentStream, new GraphicState());
Content = new PageContentContext(contentStream, new GraphicsState());
}

internal PdfReference Parent
Expand Down
3 changes: 3 additions & 0 deletions src/Synercoding.FileFormats.Pdf/PdfWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ private void _writePageAndResourcesToObjectStream(PdfPage page)
foreach (var (separation, (_, refId)) in page.Resources.SeparationReferences)
_objectStream.Write(refId, separation);

foreach (var (state, (_, refId)) in page.Resources.ExtendedGraphicsStates)
_objectStream.Write(refId, state);

_objectStream.Write(page.Content.RawContentStream);
}

Expand Down
Loading