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);
}