diff --git a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs index 9b88f0f..85c4835 100644 --- a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs +++ b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs @@ -1,6 +1,7 @@ using Synercoding.FileFormats.Pdf.Extensions; using Synercoding.FileFormats.Pdf.LowLevel.Graphics; using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using Synercoding.FileFormats.Pdf.LowLevel.Text; using Synercoding.Primitives; using Synercoding.Primitives.Extensions; @@ -151,8 +152,8 @@ public static void Main(string[] args) { var scale = (double)forestImage.Width / forestImage.Height; - var matrix = Matrix.CreateScaleMatrix(new Value(scale * 303, Unit.Millimeters), new Value(303, Unit.Millimeters)) - .Translate(new Value(-100, Unit.Millimeters), new Value(0, Unit.Millimeters)); + var matrix = Matrix.CreateScaleMatrix(new Value(scale * 303, Unit.Millimeters).AsRaw(Unit.Points), new Value(303, Unit.Millimeters).AsRaw(Unit.Points)) + .Translate(new Value(-100, Unit.Millimeters).AsRaw(Unit.Points), new Value(0, Unit.Millimeters).AsRaw(Unit.Points)); page.AddImage(forestImage, matrix); } @@ -175,6 +176,26 @@ public static void Main(string[] args) page.AddImage(reusedImage, new Rectangle(0, 0, scale * 303, 303, Unit.Millimeters)); }); } + + + writer.AddPage(page => + { + page.MediaBox = mediaBox; + page.TrimBox = trimBox; + + var scale = (double)blurImage.Width / blurImage.Height; + + page.AddImage(reusedImage, new Rectangle(0, 0, scale * 303, 303, Unit.Millimeters)); + + page.AddShapes(trimBox, static (trim, context) => + { + context.DefaultState(state => + { + state.Stroke = new SpotColor(new Separation(LowLevel.PdfName.Get("CutContour"), PredefinedColors.Magenta), 1); + }); + context.NewPath().Rectangle(trim); + }); + }); } } } diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs index 2eead27..e24e524 100644 --- a/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs +++ b/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs @@ -1,7 +1,6 @@ using Synercoding.FileFormats.Pdf.Internals; using Synercoding.FileFormats.Pdf.LowLevel.Graphics; using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; -using Synercoding.FileFormats.Pdf.LowLevel.Operators.Color; using Synercoding.FileFormats.Pdf.LowLevel.Operators.State; using Synercoding.FileFormats.Pdf.LowLevel.Text; using Synercoding.Primitives; diff --git a/src/Synercoding.FileFormats.Pdf/Internals/Path.cs b/src/Synercoding.FileFormats.Pdf/Internals/Path.cs index 1c152da..df8ecf6 100644 --- a/src/Synercoding.FileFormats.Pdf/Internals/Path.cs +++ b/src/Synercoding.FileFormats.Pdf/Internals/Path.cs @@ -40,50 +40,19 @@ internal void FinishPath() { if (GraphicsState.Stroke is not null && GraphicsState.Fill is not null) { - if (GraphicsState.Stroke is GrayColor gs) - _contentStream.Write(new GrayStrokingColorOperator(gs)); - else if (GraphicsState.Stroke is RgbColor rs) - _contentStream.Write(new RgbStrokingColorOperator(rs)); - else if (GraphicsState.Stroke is CmykColor cs) - _contentStream.Write(new CmykStrokingColorOperator(cs)); - else - throw new NotImplementedException($"The color type {GraphicsState.Stroke.GetType().Name} is not implemented."); - - if (GraphicsState.Fill is GrayColor gf) - _contentStream.Write(new GrayNonStrokingColorOperator(gf)); - else if (GraphicsState.Fill is RgbColor rf) - _contentStream.Write(new RgbNonStrokingColorOperator(rf)); - else if (GraphicsState.Fill is CmykColor cf) - _contentStream.Write(new CmykNonStrokingColorOperator(cf)); - else - throw new NotImplementedException($"The color type {GraphicsState.Fill.GetType().Name} is not implemented."); + _contentStream.SetColorFill(GraphicsState.Fill); + _contentStream.SetColorStroke(GraphicsState.Stroke); _contentStream.Write(new FillAndStrokeOperator(GraphicsState.FillRule)); } else if (GraphicsState.Fill is not null) { - if (GraphicsState.Fill is GrayColor gf) - _contentStream.Write(new GrayNonStrokingColorOperator(gf)); - else if (GraphicsState.Fill is RgbColor rf) - _contentStream.Write(new RgbNonStrokingColorOperator(rf)); - else if (GraphicsState.Fill is CmykColor cf) - _contentStream.Write(new CmykNonStrokingColorOperator(cf)); - else - throw new NotImplementedException($"The color type {GraphicsState.Fill.GetType().Name} is not implemented."); - + _contentStream.SetColorFill(GraphicsState.Fill); _contentStream.Write(new FillOperator(GraphicsState.FillRule)); } else if (GraphicsState.Stroke is not null) { - if (GraphicsState.Stroke is GrayColor gs) - _contentStream.Write(new GrayStrokingColorOperator(gs)); - else if (GraphicsState.Stroke is RgbColor rs) - _contentStream.Write(new RgbStrokingColorOperator(rs)); - else if (GraphicsState.Stroke is CmykColor cs) - _contentStream.Write(new CmykStrokingColorOperator(cs)); - else - throw new NotImplementedException($"The color type {GraphicsState.Stroke.GetType().Name} is not implemented."); - + _contentStream.SetColorStroke(GraphicsState.Stroke); _contentStream.Write(new StrokeOperator()); } else diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs index 7e2039e..17f295f 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs @@ -26,24 +26,14 @@ public sealed class ContentStream : IPdfObject, IDisposable private const string UNKNOWN_FILL_RULE = "Unknown fill rule"; private readonly PdfStream _streamWrapper; - /// - /// Constructor for - /// - /// The of this content stream - public ContentStream(PdfReference id) - : this(id, new PdfStream(new MemoryStream())) - { } - - /// - /// Constructor for - /// - /// The of this content stream - /// The to write to - public ContentStream(PdfReference id, PdfStream pdfStream) + internal ContentStream(PdfReference id, PageResources pageResources) { + Resources = pageResources; + _streamWrapper = new PdfStream(new MemoryStream()); + Reference = id; - _streamWrapper = pdfStream; } + internal PageResources Resources { get; } /// public PdfReference Reference { get; } @@ -572,6 +562,7 @@ public ContentStream SetColorFill(Color color) GrayColor gray => Write(new GrayNonStrokingColorOperator(gray)), RgbColor rgb => Write(new RgbNonStrokingColorOperator(rgb)), CmykColor cmyk => Write(new CmykNonStrokingColorOperator(cmyk)), + SpotColor spot => Write(new SpotNonStrokingColorOperator(spot)), _ => throw new NotImplementedException($"The color type {color.GetType().Name} is not implemented.") }; } @@ -589,6 +580,7 @@ public ContentStream SetColorStroke(Color color) GrayColor gray => Write(new GrayStrokingColorOperator(gray)), RgbColor rgb => Write(new RgbStrokingColorOperator(rgb)), CmykColor cmyk => Write(new CmykStrokingColorOperator(cmyk)), + SpotColor spot => Write(new SpotStrokingColorOperator(spot)), _ => throw new NotImplementedException($"The color type {color.GetType().Name} is not implemented.") }; } @@ -743,6 +735,50 @@ public ContentStream Write(CmykNonStrokingColorOperator op) return this; } + /// + /// Write the operator (SCN) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(SpotStrokingColorOperator op) + { + var name = Resources.AddSeparation(op.Color.Separation); + + _streamWrapper + .Write(name) + .Space() + .Write("CS") + .Space() + .Write(op.Color.Tint) + .Space() + .Write("SCN") + .NewLine(); + + return this; + } + + /// + /// Write the operator (scn) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(SpotNonStrokingColorOperator op) + { + var name = Resources.AddSeparation(op.Color.Separation); + + _streamWrapper + .Write(name) + .Space() + .Write("cs") + .Space() + .Write(op.Color.Tint) + .Space() + .Write("scn") + .NewLine(); + + return this; + } + /// /// Write the operator (w) to the stream /// diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs index 5b5b622..3b926a1 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs @@ -1,3 +1,5 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + namespace Synercoding.FileFormats.Pdf.LowLevel.Extensions { /// @@ -8,6 +10,34 @@ public static class PdfStreamArrayExtensions private const byte BRACKET_OPEN = 0x5B; // [ private const byte BRACKET_CLOSE = 0x5D; // ] + internal static PdfStream Write(this PdfStream stream, Separation separation, PdfReference reference) + { + return stream + .StartObject(reference) + .WriteByte(BRACKET_OPEN) + .Write(PdfName.Get("Separation")) + .Write(separation.Name) + .Write(separation.BasedOnColor.Colorspace.Name) + .Dictionary(separation.BasedOnColor, static (color, dict) => + { + var c0 = new double[color.Colorspace.Components]; + var c1 = color.Components; + var range = new double[color.Colorspace.Components * 2]; + for (int i = 1; i < range.Length; i += 2) + range[i] = 1; + + dict.Write(PdfName.Get("C0"), c0) + .Write(PdfName.Get("C1"), c1) + .Write(PdfName.Get("Domain"), 0, 1) + .Write(PdfName.Get("FunctionType"), 2) + .Write(PdfName.Get("N"), 1.0) + .Write(PdfName.Get("Range"), range); + }) + .WriteByte(BRACKET_CLOSE) + .EndObject() + .NewLine(); + } + /// /// Write an array of numbers to the pdf stream /// @@ -175,13 +205,13 @@ public static PdfStream Write(this PdfStream stream, double number1, double numb /// The sixth number in the array /// The seventh number in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - double number1, - double number2, - double number3, - double number4, - double number5, - double number6, + public static PdfStream Write(this PdfStream stream, + double number1, + double number2, + double number3, + double number4, + double number5, + double number6, double number7) { stream @@ -219,14 +249,14 @@ public static PdfStream Write(this PdfStream stream, /// The seventh number in the array /// The eigth number in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - double number1, - double number2, - double number3, - double number4, - double number5, - double number6, - double number7, + public static PdfStream Write(this PdfStream stream, + double number1, + double number2, + double number3, + double number4, + double number5, + double number6, + double number7, double number8) { stream @@ -420,13 +450,13 @@ public static PdfStream Write(this PdfStream stream, int number1, int number2, i /// The sixth number in the array /// The seventh number in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - int number1, - int number2, - int number3, - int number4, - int number5, - int number6, + public static PdfStream Write(this PdfStream stream, + int number1, + int number2, + int number3, + int number4, + int number5, + int number6, int number7) { stream @@ -464,14 +494,14 @@ public static PdfStream Write(this PdfStream stream, /// The seventh number in the array /// The eigth number in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - int number1, - int number2, - int number3, - int number4, - int number5, - int number6, - int number7, + public static PdfStream Write(this PdfStream stream, + int number1, + int number2, + int number3, + int number4, + int number5, + int number6, + int number7, int number8) { stream @@ -573,10 +603,10 @@ public static PdfStream Write(this PdfStream stream, PdfReference reference1, Pd /// The third reference in the array /// The fourth reference in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - PdfReference reference1, - PdfReference reference2, - PdfReference reference3, + public static PdfStream Write(this PdfStream stream, + PdfReference reference1, + PdfReference reference2, + PdfReference reference3, PdfReference reference4) { stream @@ -605,11 +635,11 @@ public static PdfStream Write(this PdfStream stream, /// The fourth reference in the array /// The fifth reference in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - PdfReference reference1, - PdfReference reference2, + public static PdfStream Write(this PdfStream stream, + PdfReference reference1, + PdfReference reference2, PdfReference reference3, - PdfReference reference4, + PdfReference reference4, PdfReference reference5) { stream @@ -641,12 +671,12 @@ public static PdfStream Write(this PdfStream stream, /// The fifth reference in the array /// The sixth reference in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - PdfReference reference1, - PdfReference reference2, - PdfReference reference3, - PdfReference reference4, - PdfReference reference5, + public static PdfStream Write(this PdfStream stream, + PdfReference reference1, + PdfReference reference2, + PdfReference reference3, + PdfReference reference4, + PdfReference reference5, PdfReference reference6) { stream @@ -681,13 +711,13 @@ public static PdfStream Write(this PdfStream stream, /// The sixth reference in the array /// The seventh reference in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - PdfReference reference1, - PdfReference reference2, - PdfReference reference3, - PdfReference reference4, - PdfReference reference5, - PdfReference reference6, + public static PdfStream Write(this PdfStream stream, + PdfReference reference1, + PdfReference reference2, + PdfReference reference3, + PdfReference reference4, + PdfReference reference5, + PdfReference reference6, PdfReference reference7) { stream @@ -725,14 +755,14 @@ public static PdfStream Write(this PdfStream stream, /// The seventh reference in the array /// The eigth reference in the array /// The to support chaining operations. - public static PdfStream Write(this PdfStream stream, - PdfReference reference1, - PdfReference reference2, - PdfReference reference3, - PdfReference reference4, - PdfReference reference5, - PdfReference reference6, - PdfReference reference7, + public static PdfStream Write(this PdfStream stream, + PdfReference reference1, + PdfReference reference2, + PdfReference reference3, + PdfReference reference4, + PdfReference reference5, + PdfReference reference6, + PdfReference reference7, PdfReference reference8) { stream diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs index daa6769..9f56b0b 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs @@ -73,15 +73,13 @@ public static PdfStream Dictionary(this PdfStream stream, T data, Action') - .Write('>') - .NewLine(); + .Write('>'); return stream; } @@ -184,12 +182,11 @@ private static int _toOctal(int number) int resultNumber = 0; int quotient = number; - int remainder = 0; int multiplier = 1; do { - remainder = quotient % 8; + int remainder = quotient % 8; quotient = quotient / 8; resultNumber += multiplier * remainder; @@ -216,6 +213,7 @@ internal static PdfStream StartObject(this PdfStream stream, PdfReference object internal static PdfStream EndObject(this PdfStream stream) { return stream + .NewLine() .Write('e') .Write('n') .Write('d') diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs index f6b06e2..dd70b99 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs @@ -1,3 +1,4 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using System; namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors @@ -99,6 +100,13 @@ public double Key } } + /// + public override ColorSpace Colorspace + => DeviceCMYK.Instance; + + /// + public override double[] Components => new double[] { Cyan, Magenta, Yellow, Key }; + /// public bool Equals(CmykColor? other) { @@ -111,11 +119,11 @@ public bool Equals(CmykColor? other) /// public override bool Equals(Color? other) - => other is CmykColor color && Equals(color); + => Equals(other as CmykColor); /// public override bool Equals(object? obj) - => obj is CmykColor color && Equals(color); + => Equals(obj as CmykColor); /// public override int GetHashCode() diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs index a87d4a3..2d9e053 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs @@ -1,3 +1,4 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using System; namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors @@ -7,6 +8,16 @@ namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors /// public abstract class Color : IEquatable { + /// + /// The of this . + /// + public abstract ColorSpace Colorspace { get; } + + /// + /// Retrieve the components that make up this color. + /// + public abstract double[] Components { get; } + /// public abstract bool Equals(Color? other); @@ -17,7 +28,7 @@ public abstract class Color : IEquatable public abstract override int GetHashCode(); /// - /// Indicates whether the left Color is equal to the right Color. + /// Indicates whether the left is equal to the right . /// /// The on the left side of the == /// The on the right side of the == @@ -26,7 +37,7 @@ public abstract class Color : IEquatable => left.Equals(right); /// - /// Indicates whether the left Color is not equal to the right Color. + /// Indicates whether the left is not equal to the right . /// /// The on the left side of the != /// The on the right side of the != diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/ColorSpace.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/ColorSpace.cs new file mode 100644 index 0000000..7f643ff --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/ColorSpace.cs @@ -0,0 +1,28 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + +/// +/// Class representing a colorspace +/// +public abstract class ColorSpace : IEquatable +{ + /// + /// The number of components for this colorspace + /// + public abstract int Components { get; } + + /// + /// The name of this colorspace + /// + public abstract PdfName Name { get; } + + /// + public abstract bool Equals(ColorSpace? other); + + /// + public abstract override bool Equals(object? obj); + + /// + public abstract override int GetHashCode(); +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceCMYK.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceCMYK.cs new file mode 100644 index 0000000..2030552 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceCMYK.cs @@ -0,0 +1,40 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + +/// +/// Class representing DeviceCMYK colorspace +/// +public sealed class DeviceCMYK : ColorSpace, IEquatable +{ + private DeviceCMYK() { } + + /// + /// Instance of class + /// + public static DeviceCMYK Instance { get; } = new DeviceCMYK(); + + /// + public override int Components + => 4; + + /// + public override PdfName Name + => PdfName.Get("DeviceCMYK"); + + /// + public bool Equals(DeviceCMYK? other) + => other is not null; + + /// + public override bool Equals(object? obj) + => Equals(obj as DeviceCMYK); + + /// + public override bool Equals(ColorSpace? other) + => Equals(other as DeviceCMYK); + + /// + public override int GetHashCode() + => HashCode.Combine(Instance, Components); +} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceGray.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceGray.cs new file mode 100644 index 0000000..cbba8ed --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceGray.cs @@ -0,0 +1,40 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + +/// +/// Class representing DeviceGray colorspace +/// +public sealed class DeviceGray : ColorSpace, IEquatable +{ + private DeviceGray() { } + + /// + /// Instance of class + /// + public static DeviceGray Instance { get; } = new DeviceGray(); + + /// + public override int Components + => 1; + + /// + public override PdfName Name + => PdfName.Get("DeviceGray"); + + /// + public bool Equals(DeviceGray? other) + => other is not null; + + /// + public override bool Equals(object? obj) + => Equals(obj as DeviceGray); + + /// + public override bool Equals(ColorSpace? other) + => Equals(other as DeviceGray); + + /// + public override int GetHashCode() + => HashCode.Combine(Instance, Components); +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceRGB.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceRGB.cs new file mode 100644 index 0000000..2ccc4fe --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/DeviceRGB.cs @@ -0,0 +1,40 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + +/// +/// Class representing DeviceRGB colorspace +/// +public sealed class DeviceRGB : ColorSpace, IEquatable +{ + private DeviceRGB() { } + + /// + /// Instance of class + /// + public static DeviceRGB Instance { get; } = new DeviceRGB(); + + /// + public override int Components + => 3; + + /// + public override PdfName Name + => PdfName.Get("DeviceRgb"); + + /// + public bool Equals(DeviceRGB? other) + => other is not null; + + /// + public override bool Equals(object? obj) + => Equals(obj as DeviceRGB); + + /// + public override bool Equals(ColorSpace? other) + => Equals(other as DeviceRGB); + + /// + public override int GetHashCode() + => HashCode.Combine(Instance, Components); +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/Separation.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/Separation.cs new file mode 100644 index 0000000..e42f633 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/ColorSpaces/Separation.cs @@ -0,0 +1,49 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; + +/// +/// Class representing a separation colorspace +/// +public sealed class Separation : ColorSpace, IEquatable +{ + /// + /// Constructor for . + /// + /// The name of this separation + /// The color this separation will be based on. + public Separation(PdfName name, Color baseColor) + { + Name = name; + BasedOnColor = baseColor; + } + + /// + /// The color this separation is based upon. + /// + public Color BasedOnColor { get; } + + /// + public override int Components => 1; + + /// + public override PdfName Name { get; } + + /// + public bool Equals(Separation? other) + => other is not null + && other.BasedOnColor.Equals(BasedOnColor) + && other.Name.Equals(Name); + + /// + public override bool Equals(object? obj) + => Equals(obj as Separation); + + /// + public override bool Equals(ColorSpace? other) + => Equals(other as Separation); + + /// + public override int GetHashCode() + => HashCode.Combine(BasedOnColor, Name); +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs index 432fd7a..5764a80 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs @@ -1,3 +1,4 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using System; namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors @@ -37,6 +38,13 @@ public double Gray } } + /// + public override ColorSpace Colorspace + => DeviceGray.Instance; + + /// + public override double[] Components => new double[] { Gray }; + /// public bool Equals(GrayColor? other) { @@ -46,11 +54,11 @@ public bool Equals(GrayColor? other) /// public override bool Equals(Color? other) - => other is GrayColor color && Equals(color); + => Equals(other as GrayColor); /// public override bool Equals(object? obj) - => obj is GrayColor color && Equals(color); + => Equals(obj as GrayColor); /// public override int GetHashCode() diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs index e4fa003..0a54600 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs @@ -1,3 +1,4 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using System; namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors @@ -89,6 +90,13 @@ public double Blue } } + /// + public override double[] Components => new double[] { Red, Green, Blue }; + + /// + public override ColorSpace Colorspace + => DeviceRGB.Instance; + /// public bool Equals(RgbColor? other) { @@ -100,11 +108,11 @@ public bool Equals(RgbColor? other) /// public override bool Equals(Color? other) - => other is RgbColor color && Equals(color); + => Equals(other as RgbColor); /// public override bool Equals(object? obj) - => obj is RgbColor color && Equals(color); + => Equals(obj as RgbColor); /// public override int GetHashCode() diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/SpotColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/SpotColor.cs new file mode 100644 index 0000000..1ebf9c5 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/SpotColor.cs @@ -0,0 +1,59 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +/// +/// Class representing a spotcolor +/// +public class SpotColor : Color, IEquatable +{ + /// + /// Constructor for a + /// + /// The colorspace of this spotcolor + /// The tint of this spotcolor + /// Throws if the < 0 or > 1 + public SpotColor(Separation separation, double tint) + { + if(tint < 0 || tint > 1) + throw new ArgumentOutOfRangeException(nameof(tint), $"Parameter {nameof(tint)} must be between 0.0 and 1.0 (inclusive)."); + Separation = separation; + Tint = tint; + } + + /// + /// The separation color space used + /// + public Separation Separation { get; } + + /// + /// The tint of this spotcolor + /// + public double Tint { get; } + + /// + public override ColorSpace Colorspace + => Separation; + + /// + public override double[] Components => new double[] { Tint }; + + /// + public bool Equals(SpotColor? other) + => other is not null + && other.Separation.Equals(Separation) + && other.Tint == Tint; + + /// + public override bool Equals(Color? other) + => Equals(other as SpotColor); + + /// + public override bool Equals(object? obj) + => Equals(obj as SpotColor); + + /// + public override int GetHashCode() + => HashCode.Combine(Separation, Tint); +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotNonStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotNonStrokingColorOperator.cs new file mode 100644 index 0000000..5e3ec02 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotNonStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a spot color non stroking operator (scn) + /// + public struct SpotNonStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public SpotNonStrokingColorOperator(SpotColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public SpotColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotStrokingColorOperator.cs new file mode 100644 index 0000000..0204562 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/SpotStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a spot color stroking operator (SCN) + /// + public struct SpotStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public SpotStrokingColorOperator(SpotColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public SpotColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs index eb3035d..a37ae08 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs @@ -1,4 +1,5 @@ using Synercoding.FileFormats.Pdf.Internals; +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors.ColorSpaces; using Synercoding.FileFormats.Pdf.LowLevel.Text; using Synercoding.FileFormats.Pdf.LowLevel.XRef; using System; @@ -11,20 +12,31 @@ internal sealed class PageResources : IDisposable { private readonly TableBuilder _tableBuilder; private readonly Map _images; + private readonly Dictionary _separations; private readonly Dictionary _standardFonts; + private int _separationCounter = 0; private int _imageCounter = 0; internal PageResources(TableBuilder tableBuilder) { _tableBuilder = tableBuilder; _images = new Map(); + _separations = new Dictionary(); _standardFonts = new Dictionary(); } public IReadOnlyDictionary Images => _images.Forward; + internal IReadOnlyDictionary SeparationReferences + => _separations; + + internal IReadOnlyCollection<(Type1StandardFont Font, PdfReference Reference)> FontReferences + => _standardFonts + .Select(kv => (kv.Key, kv.Value)) + .ToArray(); + public void Dispose() { foreach (var kv in _images) @@ -33,7 +45,7 @@ public void Dispose() _images.Clear(); } - public PdfName AddImageToResources(Image image) + public PdfName AddImage(Image image) { if (_images.Reverse.Contains(image)) return _images.Reverse[image]; @@ -47,20 +59,24 @@ public PdfName AddImageToResources(Image image) return pdfName; } - internal void MarkStdFontAsUsed(Type1StandardFont font) + internal PdfName AddStandardFont(Type1StandardFont font) { if (!_standardFonts.ContainsKey(font)) _standardFonts[font] = _tableBuilder.ReserveId(); + + return font.LookupName; } - internal IReadOnlyCollection<(Type1StandardFont Font, PdfReference Reference)> FontReferences + internal PdfName AddSeparation(Separation separation) { - get - { - return _standardFonts - .Select(kv => (kv.Key, kv.Value)) - .ToArray(); - } + if (_separations.TryGetValue(separation, out var tuple)) + return tuple.Name; + + var key = "Sep" + System.Threading.Interlocked.Increment(ref _separationCounter).ToString().PadLeft(6, '0'); + var name = PdfName.Get(key); + _separations[separation] = (name, _tableBuilder.ReserveId()); + + return name; } } } diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs index 6df9e7c..93e79b6 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs @@ -1,5 +1,4 @@ using Synercoding.FileFormats.Pdf.LowLevel.Extensions; -using Synercoding.FileFormats.Pdf.LowLevel.Text; using Synercoding.FileFormats.Pdf.LowLevel.XRef; using System; using System.Collections.Generic; @@ -25,6 +24,9 @@ public PageTree(PdfReference id, TableBuilder tableBuilder) public void AddPage(PdfPage pdfObject) => _pages.Add(pdfObject); + public int PageCount + => _pages.Count; + internal uint WriteToStream(PdfStream stream) { if (_isWritten) diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs index a92f101..250a065 100644 --- a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs @@ -1,6 +1,7 @@ using Synercoding.FileFormats.Pdf.LowLevel.Extensions; using Synercoding.Primitives; using System; +using System.Runtime.Serialization; namespace Synercoding.FileFormats.Pdf.LowLevel { @@ -20,6 +21,38 @@ public PdfDictionary(PdfStream stream) _stream = stream; } + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The array to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int[] numbers) + { + _stream + .Write(key) + .Space() + .Write(numbers); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The array to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double[] numbers) + { + _stream + .Write(key) + .Space() + .Write(numbers); + + return this; + } + /// /// Write an array of s to the dictionary /// @@ -31,8 +64,7 @@ public PdfDictionary Write(PdfName key, PdfReference[] objectReferences) _stream .Write(key) .Space() - .Write(objectReferences) - .NewLine(); + .Write(objectReferences); return this; } @@ -48,8 +80,7 @@ public PdfDictionary Write(PdfName key, double value) _stream .Write(key) .Space() - .Write(value) - .NewLine(); + .Write(value); return this; } @@ -66,8 +97,7 @@ public PdfDictionary Write(PdfName key, double value1, double value2) _stream .Write(key) .Space() - .Write(value1, value2) - .NewLine(); + .Write(value1, value2); return this; } @@ -85,8 +115,7 @@ public PdfDictionary Write(PdfName key, double value1, double value2, double val _stream .Write(key) .Space() - .Write(value1, value2, value3) - .NewLine(); + .Write(value1, value2, value3); return this; } @@ -105,8 +134,7 @@ public PdfDictionary Write(PdfName key, double value1, double value2, double val _stream .Write(key) .Space() - .Write(value1, value2, value3, value4) - .NewLine(); + .Write(value1, value2, value3, value4); return this; } @@ -126,8 +154,7 @@ public PdfDictionary Write(PdfName key, double value1, double value2, double val _stream .Write(key) .Space() - .Write(value1, value2, value3, value4, value5) - .NewLine(); + .Write(value1, value2, value3, value4, value5); return this; } @@ -148,8 +175,7 @@ public PdfDictionary Write(PdfName key, double value1, double value2, double val _stream .Write(key) .Space() - .Write(value1, value2, value3, value4, value5, value6) - .NewLine(); + .Write(value1, value2, value3, value4, value5, value6); return this; } @@ -165,8 +191,7 @@ public PdfDictionary Write(PdfName key, long value) _stream .Write(key) .Space() - .Write(value) - .NewLine(); + .Write(value); return this; } @@ -182,8 +207,7 @@ public PdfDictionary Write(PdfName key, int value) _stream .Write(key) .Space() - .Write(value) - .NewLine(); + .Write(value); return this; } @@ -200,8 +224,7 @@ public PdfDictionary Write(PdfName key, int value1, int value2) _stream .Write(key) .Space() - .Write(value1, value2) - .NewLine(); + .Write(value1, value2); return this; } @@ -219,8 +242,7 @@ public PdfDictionary Write(PdfName key, int value1, int value2, int value3) _stream .Write(key) .Space() - .Write(value1, value2, value3) - .NewLine(); + .Write(value1, value2, value3); return this; } @@ -239,8 +261,7 @@ public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int _stream .Write(key) .Space() - .Write(value1, value2, value3, value4) - .NewLine(); + .Write(value1, value2, value3, value4); return this; } @@ -260,8 +281,7 @@ public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int _stream .Write(key) .Space() - .Write(value1, value2, value3, value4, value5) - .NewLine(); + .Write(value1, value2, value3, value4, value5); return this; } @@ -282,8 +302,7 @@ public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int _stream .Write(key) .Space() - .Write(value1, value2, value3, value4, value5, value6) - .NewLine(); + .Write(value1, value2, value3, value4, value5, value6); return this; } @@ -299,8 +318,7 @@ public PdfDictionary Write(PdfName key, string value) _stream .Write(key) .Space() - .Write(value) - .NewLine(); + .Write(value); return this; } @@ -316,8 +334,7 @@ public PdfDictionary Write(PdfName key, PdfName value) _stream .Write(key) .Space() - .Write(value) - .NewLine(); + .Write(value); return this; } @@ -334,8 +351,7 @@ public PdfDictionary Write(PdfName key, PdfReference objectReference) _stream .Write(key) .Space() - .Write(objectReference) - .NewLine(); + .Write(objectReference); return this; } @@ -356,8 +372,6 @@ public PdfDictionary Write(PdfName key, T data, Action rawActio rawActions(data, _stream); - _stream.NewLine(); - return this; } @@ -381,8 +395,7 @@ public PdfDictionary Write(PdfName key, Rectangle rectangle) _stream .Write(key) .Space() - .Write(rectangle) - .NewLine(); + .Write(rectangle); return this; } @@ -435,8 +448,7 @@ internal PdfDictionary Type(ObjectType objectType) _stream .Write(PdfName.Get("Type")) .Space() - .Write(nameValue) - .NewLine(); + .Write(nameValue); return this; } @@ -452,8 +464,7 @@ internal PdfDictionary SubType(XObjectSubType subType) _stream .Write(PdfName.Get("Subtype")) .Space() - .Write(nameValue) - .NewLine(); + .Write(nameValue); return this; } @@ -469,8 +480,7 @@ internal PdfDictionary SubType(FontSubType subType) _stream .Write(PdfName.Get("Subtype")) .Space() - .Write(nameValue) - .NewLine(); + .Write(nameValue); return this; } diff --git a/src/Synercoding.FileFormats.Pdf/Matrix.cs b/src/Synercoding.FileFormats.Pdf/Matrix.cs index 31f1e8b..797e258 100644 --- a/src/Synercoding.FileFormats.Pdf/Matrix.cs +++ b/src/Synercoding.FileFormats.Pdf/Matrix.cs @@ -1,4 +1,3 @@ -using Synercoding.Primitives; using System; using System.Runtime.CompilerServices; @@ -146,15 +145,6 @@ public Matrix FlipVertical() public Matrix Scale(double x, double y) => Multiply(CreateScaleMatrix(x, y)); - /// - /// Apply a scale operation on the matrix - /// - /// The X amount to scale - /// The Y amount to scale - /// The new - public Matrix Scale(Value x, Value y) - => Multiply(CreateScaleMatrix(x, y)); - /// /// Apply a skew operation on the matrix /// @@ -173,15 +163,6 @@ public Matrix Skew(double a, double b) public Matrix Translate(double x, double y) => Multiply(CreateTranslationMatrix(x, y)); - /// - /// Apply a translation operation on the matrix - /// - /// The X distance to translate - /// The Y distance to translate - /// The new - public Matrix Translate(Value x, Value y) - => Multiply(CreateTranslationMatrix(x, y)); - /// /// Multiply matrix with /// @@ -232,18 +213,6 @@ public static Matrix CreateScaleMatrix(double x, double y) 0, y, 0, 0); - /// - /// Create a matrix used for scaling - /// - /// The X scale - /// The Y scale - /// A scaled matrix - public static Matrix CreateScaleMatrix(Value x, Value y) - => new Matrix( - x.ConvertTo(Unit.Points).Raw, 0, - 0, y.ConvertTo(Unit.Points).Raw, - 0, 0); - /// /// Create a matrix used for skewing /// @@ -268,18 +237,6 @@ public static Matrix CreateTranslationMatrix(double x, double y) 0, 1, x, y); - /// - /// Create a matrix used for translation - /// - /// The amount of X translation - /// The amount of Y translation - /// A matrix representing a translation - public static Matrix CreateTranslationMatrix(Value x, Value y) - => new Matrix( - 1, 0, - 0, 1, - x.ConvertTo(Unit.Points).Raw, y.ConvertTo(Unit.Points).Raw); - [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double _degreeToRad(double degree) => degree * Math.PI / 180; diff --git a/src/Synercoding.FileFormats.Pdf/PdfPage.cs b/src/Synercoding.FileFormats.Pdf/PdfPage.cs index 50e0b4f..a42f9d2 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfPage.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfPage.cs @@ -5,8 +5,6 @@ using Synercoding.Primitives; using Synercoding.Primitives.Extensions; using System; -using System.Collections.Generic; -using System.Linq; namespace Synercoding.FileFormats.Pdf { @@ -27,13 +25,19 @@ internal PdfPage(TableBuilder tableBuilder, PageTree parent) _parent = parent; _parent.AddPage(this); + PageNumber = _parent.PageCount; Reference = tableBuilder.ReserveId(); - ContentStream = new ContentStream(tableBuilder.ReserveId()); Resources = new PageResources(_tableBuilder); + ContentStream = new ContentStream(tableBuilder.ReserveId(), Resources); } internal PageResources Resources { get; } + /// + /// The number of the page + /// + public int PageNumber { get; } + /// /// The content stream of this page /// @@ -93,7 +97,7 @@ public PdfName AddImageToResources(SixLabors.ImageSharp.Image image) var pdfImage = new Image(id, image); - return Resources.AddImageToResources(pdfImage); + return Resources.AddImage(pdfImage); } /// @@ -109,7 +113,7 @@ public PdfName AddImageToResources(System.IO.Stream jpgStream, int width, int he var pdfImage = new Image(id, jpgStream, width, height); - return Resources.AddImageToResources(pdfImage); + return Resources.AddImage(pdfImage); } /// @@ -130,7 +134,7 @@ public PdfName AddImageToResources(System.IO.Stream imageStream) /// The that can be used to reference this image in the public PdfName AddImageToResources(Image image) { - return Resources.AddImageToResources(image); + return Resources.AddImage(image); } /// @@ -143,7 +147,7 @@ public void Dispose() internal void MarkStdFontAsUsed(Type1StandardFont font) { - Resources.MarkStdFontAsUsed(font); + Resources.AddStandardFont(font); } internal uint WriteToStream(PdfStream stream) @@ -194,6 +198,17 @@ internal uint WriteToStream(PdfStream stream) } })); } + + if (resources.SeparationReferences.Count != 0) + { + stream.Write(PdfName.Get("ColorSpace"), resources.SeparationReferences.Values, static (separations, stream) => stream.Dictionary(separations, static (separations, colorspaceDictionary) => + { + foreach (var (name, reference) in separations) + { + colorspaceDictionary.Write(name, reference); + } + })); + } })); // Content stream @@ -221,6 +236,12 @@ internal uint WriteToStream(PdfStream stream) .Write(PdfName.Get("BaseFont"), font.Name); }); } + foreach (var (separation, (_, refId)) in Resources.SeparationReferences) + { + _tableBuilder.SetPosition(refId, stream.Position); + + stream.Write(separation, refId); + } if (!ContentStream.IsWritten) { var dependentPosition = ContentStream.WriteToStream(stream); diff --git a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs index ffc2270..83167fd 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Reflection; +using System.Threading.Tasks; namespace Synercoding.FileFormats.Pdf { @@ -19,6 +20,8 @@ public sealed class PdfWriter : IDisposable private readonly PageTree _pageTree; private readonly Catalog _catalog; + private bool _endingWritten = false; + /// /// Constructor for /// @@ -54,6 +57,12 @@ public PdfWriter(Stream stream, bool ownsStream) /// public DocumentInformation DocumentInformation { get; } + /// + /// Returns the number of pages already added to the writer + /// + public int PageCount + => _pageTree.PageCount; + /// /// Set meta information for this document /// @@ -61,6 +70,8 @@ public PdfWriter(Stream stream, bool ownsStream) /// Returns this to chain calls public PdfWriter SetDocumentInfo(Action infoAction) { + _throwWhenEndingWritten(); + infoAction(DocumentInformation); return this; @@ -82,6 +93,8 @@ public PdfWriter AddPage(Action pageAction) /// Returns this to chain calls public PdfWriter AddPage(T data, Action pageAction) { + _throwWhenEndingWritten(); + using (var page = new PdfPage(_tableBuilder, _pageTree)) { pageAction(data, page); @@ -92,6 +105,34 @@ public PdfWriter AddPage(T data, Action pageAction) return this; } + /// + /// Add a page to the pdf file + /// + /// Action used to setup the page + /// Returns an awaitable task that resolves into this to chain calls + public async Task AddPageAsync(Func pageAction) + => await AddPageAsync(pageAction, static async (action, page) => await action(page)); + + /// + /// Add a page to the pdf file + /// + /// Data passed into the action + /// Action used to setup the page + /// Returns an awaitable task that resolves into this to chain calls + public async Task AddPageAsync(T data, Func pageAction) + { + _throwWhenEndingWritten(); + + using (var page = new PdfPage(_tableBuilder, _pageTree)) + { + await pageAction(data, page); + + page.WriteToStream(_stream); + } + + return this; + } + /// /// Add an to the pdf file and get the reference returned /// @@ -99,6 +140,8 @@ public PdfWriter AddPage(T data, Action pageAction) /// The image reference that can be used in pages public Image AddImage(SixLabors.ImageSharp.Image image) { + _throwWhenEndingWritten(); + var id = _tableBuilder.ReserveId(); var pdfImage = new Image(id, image); @@ -123,6 +166,8 @@ public Image AddImage(SixLabors.ImageSharp.Image image) /// The image reference that can be used in pages public Image AddJpgImageUnsafe(Stream jpgStream, int originalWidth, int originalHeight) { + _throwWhenEndingWritten(); + var id = _tableBuilder.ReserveId(); var pdfImage = new Image(id, jpgStream, originalWidth, originalHeight); @@ -135,9 +180,17 @@ public Image AddJpgImageUnsafe(Stream jpgStream, int originalWidth, int original return pdfImage; } - /// - public void Dispose() + /// + /// Write the PDF trailer; indicates that the PDF is done. + /// + /// + /// Other calls to this will throw or have no effect after call this. + /// + public void WriteTrailer() { + if (_endingWritten) + return; + _writePageTree(); _writeCatalog(); @@ -148,12 +201,27 @@ public void Dispose() _stream.Flush(); + _endingWritten = true; + } + + /// + public void Dispose() + { + WriteTrailer(); + + _stream.Flush(); + if (_ownsStream) { _stream.Dispose(); } } + private void _throwWhenEndingWritten() + { + if (_endingWritten) throw new InvalidOperationException("Can't change document information when PDF trailer is written to the stream."); + } + private static void _writeHeader(PdfStream stream) { stream.WriteByte(0x25); // % @@ -204,47 +272,27 @@ private void _writePdfEnding() var xRefTable = _tableBuilder.GetXRefTable(); uint xRefPosition = xRefTable.WriteToStream(_stream); - var trailer = new Trailer(xRefPosition, xRefTable.Section.ObjectCount, _catalog, DocumentInformation); - trailer.WriteToStream(_stream); + _writeTrailer(_stream, xRefPosition, xRefTable.Section.ObjectCount, _catalog.Reference, DocumentInformation.Reference); } - private readonly struct Trailer + private void _writeTrailer(PdfStream stream, uint startXRef, int size, PdfReference root, PdfReference documentInfo) { - public Trailer(uint startXRef, int size, Catalog root, DocumentInformation documentInfo) - { - StartXRef = startXRef; - Size = size; - Root = root.Reference; - DocumentInfo = documentInfo.Reference; - } - - public uint StartXRef { get; } - public int Size { get; } - public PdfReference Root { get; } - public PdfReference DocumentInfo { get; } - - internal uint WriteToStream(PdfStream stream) - { - var position = (uint)stream.Position; - - stream - .Write("trailer") - .NewLine() - .Dictionary(this, static (trailer, dictionary) => - { - dictionary - .Write(PdfName.Get("Size"), trailer.Size) - .Write(PdfName.Get("Root"), trailer.Root) - .Write(PdfName.Get("Info"), trailer.DocumentInfo); - }) - .Write("startxref") - .NewLine() - .Write(StartXRef) - .NewLine() - .Write("%%EOF"); - - return position; - } + stream + .Write("trailer") + .NewLine() + .Dictionary((size, root, documentInfo), static (triple, dictionary) => + { + var (size, root, documentInfo) = triple; + dictionary + .Write(PdfName.Get("Size"), size) + .Write(PdfName.Get("Root"), root) + .Write(PdfName.Get("Info"), documentInfo); + }) + .Write("startxref") + .NewLine() + .Write(startXRef) + .NewLine() + .Write("%%EOF"); } } }