diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index cd44a0f..7c2b432 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -45,7 +45,7 @@ jobs: run: dotnet pack -v normal -c Release --no-restore --include-symbols --include-source -p:SymbolPackageFormat=snupkg -p:PackageVersion=1.0.0-pre+$GITHUB_RUN_ID src/$PROJECT_NAME/$PROJECT_NAME.*proj - name: Upload Artifact if: matrix.os == 'ubuntu-latest' - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: nupkg path: ./artifacts/pkg/Release/${{ env.PROJECT_NAME }}.*.nupkg @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Artifact - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: nupkg - name: Push to GitHub Feed @@ -90,4 +90,4 @@ jobs: done - name: Push to NuGet Feed if: ${{ env.NUGET_FEED }} != '' - run: dotnet nuget push ./nupkg/*.nupkg --source $NUGET_FEED --skip-duplicate --api-key $NUGET_KEY \ No newline at end of file + run: dotnet nuget push ./nupkg/*.nupkg --source $NUGET_FEED --skip-duplicate --api-key $NUGET_KEY diff --git a/Directory.Build.targets b/Directory.Build.targets index 35d8d32..9a3792f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -7,7 +7,7 @@ - + diff --git a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs index b3f5be4..41d56b5 100644 --- a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs +++ b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs @@ -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(); diff --git a/src/Synercoding.FileFormats.Pdf/ExtendedGraphicsState.cs b/src/Synercoding.FileFormats.Pdf/ExtendedGraphicsState.cs new file mode 100644 index 0000000..5bd2db0 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/ExtendedGraphicsState.cs @@ -0,0 +1,23 @@ +namespace Synercoding.FileFormats.Pdf; + +/// +/// Class representing an ExtGState dictionary. +/// +public sealed record class ExtendedGraphicsState +{ + /// + /// 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 entry sets both parameters + /// unless there is also an entry in the same graphics state parameter dictionary, + /// in which case the entry sets only the overprint parameter for stroking. + /// + public bool? Overprint { get; set; } + + /// + /// A flag specifying whether to apply overprint for painting operations other than stroking. + /// If this entry is absent, the entry, if any, sets this parameter. + /// + public bool? OverprintNonStroking { get; set; } +} + diff --git a/src/Synercoding.FileFormats.Pdf/GraphicState.cs b/src/Synercoding.FileFormats.Pdf/GraphicsState.cs similarity index 97% rename from src/Synercoding.FileFormats.Pdf/GraphicState.cs rename to src/Synercoding.FileFormats.Pdf/GraphicsState.cs index f489acf..bd74b11 100644 --- a/src/Synercoding.FileFormats.Pdf/GraphicState.cs +++ b/src/Synercoding.FileFormats.Pdf/GraphicsState.cs @@ -7,9 +7,9 @@ namespace Synercoding.FileFormats.Pdf; /// /// Class representing the grahpic state of a PDF at a certain moment in time. /// -public sealed class GraphicState +public sealed class GraphicsState { - internal GraphicState() + internal GraphicsState() { CTM = Matrix.Identity; Fill = PredefinedColors.Black; @@ -111,9 +111,9 @@ internal GraphicState() /// public double TextRise { get; internal set; } - internal GraphicState Clone() + internal GraphicsState Clone() { - return new GraphicState() + return new GraphicsState() { CTM = CTM, Fill = Fill, diff --git a/src/Synercoding.FileFormats.Pdf/IContentContext.cs b/src/Synercoding.FileFormats.Pdf/IContentContext.cs index 1f4a37e..02e80b0 100644 --- a/src/Synercoding.FileFormats.Pdf/IContentContext.cs +++ b/src/Synercoding.FileFormats.Pdf/IContentContext.cs @@ -19,7 +19,7 @@ public interface IContentContext /// /// Represents the current graphic state /// - GraphicState GraphicState { get; } + GraphicsState GraphicState { get; } /// /// Wrap the in save and restore state operators @@ -40,7 +40,7 @@ public interface IContentContext Task WrapInStateAsync(T data, Func contentOperations); /// - /// Concatenate a matrix to + /// Concatenate a matrix to /// /// The matrix to concat /// This to enable chaining operations @@ -94,5 +94,12 @@ public interface IContentContext /// The dash pattern to set /// This to enable chaining operations TSelf SetDashPattern(Dash dashPattern); + + /// + /// Set an extended graphics state (ExtGState) dictionary. + /// + /// The state to apply. + /// This to enable chaining operations + TSelf SetExtendedGraphicsState(ExtendedGraphicsState extendedGraphicsState); } diff --git a/src/Synercoding.FileFormats.Pdf/ITextContentContext.cs b/src/Synercoding.FileFormats.Pdf/ITextContentContext.cs index 0fde58e..13895a0 100644 --- a/src/Synercoding.FileFormats.Pdf/ITextContentContext.cs +++ b/src/Synercoding.FileFormats.Pdf/ITextContentContext.cs @@ -101,7 +101,7 @@ public interface ITextContentContext : IContentContext ITextContentContext ShowTextOnNextLine(string text); /// - /// Operation to show text on the next line and setting the and + /// Operation to show text on the next line and setting the and /// /// The text to show /// The word spacing to set diff --git a/src/Synercoding.FileFormats.Pdf/Internals/PageContentContext.cs b/src/Synercoding.FileFormats.Pdf/Internals/PageContentContext.cs index 1174e7d..c876f5a 100644 --- a/src/Synercoding.FileFormats.Pdf/Internals/PageContentContext.cs +++ b/src/Synercoding.FileFormats.Pdf/Internals/PageContentContext.cs @@ -6,7 +6,7 @@ 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; @@ -14,7 +14,7 @@ public PageContentContext(ContentStream contentStream, GraphicState graphicState public ContentStream RawContentStream { get; } - public GraphicState GraphicState { get; } + public GraphicsState GraphicState { get; } public IPageContentContext AddImage(Image image) { @@ -155,4 +155,11 @@ public async Task AddShapesAsync(T data, Func + /// Set an extended graphics state (ExtGState) dictionary using a gs operator.. + /// + /// The state to apply. + /// The to support chaining operations. + public ContentStream SetExtendedGraphicsState(ExtendedGraphicsState state) + { + var name = Resources.AddExtendedGraphicsState(state); + + InnerStream.Write(name).Space().Write("gs").NewLine(); + + return this; + } + /// /// Write the operator (m) to the stream /// diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs index f2eb82b..a0c3946 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs @@ -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. /// /// - /// If the segments meet at too sharp an angle (see ), a bevel join shall be used instead. + /// If the segments meet at too sharp an angle (see ), a bevel join shall be used instead. /// MiterJoin = 0, /// diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs index 7fc484e..b73d780 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Internal/PageResources.cs @@ -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 _images; private readonly Dictionary _separations; private readonly Dictionary _standardFonts; + private readonly Dictionary _extendedGraphicsStates; + private int _stateCounter = 0; private int _separationCounter = 0; private int _imageCounter = 0; @@ -24,11 +27,15 @@ internal PageResources(TableBuilder tableBuilder) _images = new Map(); _separations = new Dictionary(); _standardFonts = new Dictionary(); + _extendedGraphicsStates = new Dictionary(); } public IReadOnlyDictionary Images => _images.Forward; + public IReadOnlyDictionary ExtendedGraphicsStates + => _extendedGraphicsStates; + internal IReadOnlyDictionary SeparationReferences => _separations; @@ -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; + } } diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs index cacc7af..dc6727f 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectStream.cs @@ -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 @@ -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(PdfReference reference, T data, Action dictionaryAction) { InnerStream diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs index 844001d..1c16185 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs @@ -146,6 +146,22 @@ public PdfDictionary Write(PdfName key, int value) return this; } + /// + /// Write a boolean to the dictionary + /// + /// The key of the item in the dictionary + /// The boolean to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, bool value) + { + _stream + .Write(key) + .Space() + .Write(value); + + return this; + } + /// /// Write a text to the dictionary /// diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs index 567b152..7b2cc7d 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs @@ -56,6 +56,18 @@ public PdfStream Write(char c) return WriteByte((byte)( c & 0xFF )); } + /// + /// Write a to the stream + /// + /// The boolean to write. + /// The calling to support chaining operations. + public PdfStream Write(bool b) + { + return b + ? Write("true") + : Write("false"); + } + /// /// Write a to the stream /// diff --git a/src/Synercoding.FileFormats.Pdf/PdfPage.cs b/src/Synercoding.FileFormats.Pdf/PdfPage.cs index 80a3096..067879c 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfPage.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfPage.cs @@ -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 diff --git a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs index 0d685d5..174b1a1 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs @@ -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); }