diff --git a/Directory.Build.props b/Directory.Build.props index 48b2b77..41f9818 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -48,16 +48,6 @@ - - - $(DefineConstants);SUPPORTS_CODECOVERAGE - - - - - $(DefineConstants);SUPPORTS_CODECOVERAGE - - $(DefineConstants);SUPPORTS_MATHF @@ -66,19 +56,6 @@ $(DefineConstants);SUPPORTS_NULLABLEREFATTRIBUTES - - - $(DefineConstants);SUPPORTS_MATHF - $(DefineConstants);SUPPORTS_CODECOVERAGE - - - - - $(DefineConstants);SUPPORTS_MATHF - $(DefineConstants);SUPPORTS_HASHCODE - $(DefineConstants);SUPPORTS_CODECOVERAGE - - $(DefineConstants);SUPPORTS_MATHF diff --git a/LICENSE b/LICENSE index 620872c..e6b40f4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Gerard Gunnewijk +Copyright (c) 2021 Gerard Gunnewijk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index defdd1b..6c11be8 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This project is licensed under MIT license. ## Specifications used This library was created using the specifications lay out in ["PDF 32000-1:2008, Document management – Portable document format – Part 1: PDF 1.7"](https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf). -The full specifications are not implemented. This library currently only supports placements of images and setting the different boxes. +The full specifications are not implemented. This library currently only supports placements of images, drawing of vector shapes (CMYK, RGB & gray scale), and setting the different boxes. ## Remarks Unlike most PDF libraries this library does not create the entire PDF model in memory before writing the PDF to a (file)stream. Most libaries support editing capabilities, because this libary only supports creating files, it was not necessary to keep the PDF model in memory. This results in less memory usage. diff --git a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs index b9b1a57..479ee62 100644 --- a/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs +++ b/samples/Synercoding.FileFormats.Pdf.ConsoleTester/Program.cs @@ -1,6 +1,9 @@ -using Synercoding.FileFormats.Pdf.Primitives; +using Synercoding.FileFormats.Pdf.Extensions; +using Synercoding.FileFormats.Pdf.LowLevel.Graphics; +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; using Synercoding.Primitives; using Synercoding.Primitives.Extensions; +using System; using System.IO; namespace Synercoding.FileFormats.Pdf.ConsoleTester @@ -53,6 +56,45 @@ public static void Main(string[] args) page.AddImage(eyeStream, new Rectangle(offSet, offSet, width + offSet, height + offSet, Unit.Millimeters)); } }) + // Test shape graphics + .AddPage(page => + { + page.AddShapes(ctx => + { + ctx.DefaultState(g => + { + g.LineWidth = 1; + g.Fill = null; + g.Stroke = null; + g.Dash = new Dash() + { + Array = Array.Empty(), + Phase = 0 + }; + g.MiterLimit = 10; + g.LineCap = LineCapStyle.ButtCap; + g.LineJoin = LineJoinStyle.MiterJoin; + }); + + ctx.NewPath(g => { g.Fill = PredefinedColors.Red; g.Stroke = PredefinedColors.Black; g.LineWidth = 5; }) + .Move(100, 100) + .LineTo(200, 100) + .LineTo(200, 200) + .LineTo(100, 200); + ctx.NewPath(g => { g.Fill = PredefinedColors.Blue; g.Stroke = null; }) + .Move(50, 50) + .LineTo(150, 50) + .LineTo(150, 150) + .LineTo(50, 150) + .Close(); + ctx.NewPath(g => { g.Fill = null; g.Stroke = PredefinedColors.Yellow; g.LineWidth = 3; g.Dash = new Dash() { Array = new[] { 5d } }; }) + .Move(150, 150) + .LineTo(250, 150) + .LineTo(250, 250) + .LineTo(150, 250) + .Close(); + }); + }) // Test placement using matrix .AddPage(page => { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ab42317..e365322 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ - net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.6 + net5.0;netcoreapp3.1;netstandard2.1 $(MSBuildAllProjects);$(MSBuildThisFileDirectory)..\Directory.Build.props src diff --git a/src/Synercoding.FileFormats.Pdf/DocumentInformation.cs b/src/Synercoding.FileFormats.Pdf/DocumentInformation.cs index 0122df5..0041a7d 100644 --- a/src/Synercoding.FileFormats.Pdf/DocumentInformation.cs +++ b/src/Synercoding.FileFormats.Pdf/DocumentInformation.cs @@ -1,3 +1,5 @@ +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; using System; namespace Synercoding.FileFormats.Pdf @@ -5,8 +7,15 @@ namespace Synercoding.FileFormats.Pdf /// /// This class contains information about the document /// - public class DocumentInformation + public class DocumentInformation : IPdfObject { + private bool _isWritten; + + internal DocumentInformation(PdfReference id) + { + Reference = id; + } + /// /// The document's title /// @@ -46,5 +55,87 @@ public class DocumentInformation /// The date and time the document was most recently modified, in human-readable form. /// public DateTime? ModDate { get; set; } + + /// + public PdfReference Reference { get; } + + internal uint WriteToStream(PdfStream stream) + { + if (_isWritten) + throw new InvalidOperationException("Object is already written to stream."); + + var position = (uint)stream.Position; + + stream.IndirectDictionary(this, static (did, dictionary) => + { + if (!string.IsNullOrWhiteSpace(did.Title)) + dictionary.Write(PdfName.Get("Title"), _toPdfHexadecimalString(did.Title!)); + if (!string.IsNullOrWhiteSpace(did.Author)) + dictionary.Write(PdfName.Get("Author"), _toPdfHexadecimalString(did.Author!)); + if (!string.IsNullOrWhiteSpace(did.Subject)) + dictionary.Write(PdfName.Get("Subject"), _toPdfHexadecimalString(did.Subject!)); + if (!string.IsNullOrWhiteSpace(did.Keywords)) + dictionary.Write(PdfName.Get("Keywords"), _toPdfHexadecimalString(did.Keywords!)); + if (!string.IsNullOrWhiteSpace(did.Creator)) + dictionary.Write(PdfName.Get("Creator"), _toPdfHexadecimalString(did.Creator!)); + if (!string.IsNullOrWhiteSpace(did.Producer)) + dictionary.Write(PdfName.Get("Producer"), _toPdfHexadecimalString(did.Producer!)); + if (did.CreationDate != null) + dictionary.Write(PdfName.Get("CreationDate"), _toPdfDate(did.CreationDate.Value)); + if (did.ModDate != null) + dictionary.Write(PdfName.Get("ModDate"), _toPdfDate(did.ModDate.Value)); + }); + + _isWritten = true; + + return position; + } + + private static string _toPdfHexadecimalString(string input) + { + var bytes = System.Text.Encoding.ASCII.GetBytes(input); + var builder = new System.Text.StringBuilder((bytes.Length * 2) + 2); + builder.Append('<'); + foreach (var b in bytes) + { + builder.Append(b.ToString("X2")); + } + builder.Append('>'); + return builder.ToString(); + } + + private static string _toPdfDate(DateTimeOffset input) + { + var datePart = input.ToString("yyyyMMddHHmmss"); + + var builder = new System.Text.StringBuilder(22); + builder.Append("(D:"); + builder.Append(datePart); + + var hours = input.Offset.Hours; + var minutes = input.Offset.Minutes; + + if (hours == 0 && minutes == 0) + { + builder.Append("Z00'00"); + } + else + { + if (hours > 0 || (hours == 0 && minutes > 0)) + { + builder.Append('+'); + } + else + { + builder.Append('-'); + } + builder.Append(Math.Abs(hours).ToString().PadLeft(2, '0')); + builder.Append('\''); + builder.Append(minutes.ToString().PadLeft(2, '0')); + } + builder.Append(')'); + + return builder.ToString(); + } } } diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/MatrixExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/MatrixExtensions.cs deleted file mode 100644 index 7fe1a4a..0000000 --- a/src/Synercoding.FileFormats.Pdf/Extensions/MatrixExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Synercoding.FileFormats.Pdf.Primitives; -using Synercoding.Primitives; - -namespace Synercoding.FileFormats.Pdf.Extensions -{ - /// - /// Extensions to use Values as input. These will be converted to points directly - /// - public static class MatrixExtensions - { - /// - /// 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 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); - } -} diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs new file mode 100644 index 0000000..83d66e6 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Extensions/PdfPageExtensions.cs @@ -0,0 +1,155 @@ +using Synercoding.FileFormats.Pdf.Internals; +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.Primitives; +using System; +using System.IO; + +namespace Synercoding.FileFormats.Pdf.Extensions +{ + /// + /// Extension class for + /// + public static class PdfPageExtensions + { + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement matrix + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Image image, Matrix matrix) + { + page.ContentStream + .SaveState() + .CTM(matrix) + .Paint(page.AddImageToResources(image)) + .RestoreState(); + + return page; + } + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement rectangle + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Image image, Rectangle rectangle) + => page.AddImage(image, rectangle.AsPlacementMatrix()); + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement matrix + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, SixLabors.ImageSharp.Image image, Matrix matrix) + { + page.ContentStream + .SaveState() + .CTM(matrix) + .Paint(page.AddImageToResources(image)) + .RestoreState(); + + return page; + } + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement rectangle + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, SixLabors.ImageSharp.Image image, Rectangle rectangle) + => page.AddImage(image, rectangle.AsPlacementMatrix()); + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement matrix + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Stream imageStream, Matrix matrix) + { + page.ContentStream + .SaveState() + .CTM(matrix) + .Paint(page.AddImageToResources(imageStream)) + .RestoreState(); + + return page; + } + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The placement rectangle + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Stream imageStream, Rectangle rectangle) + => page.AddImage(imageStream, rectangle.AsPlacementMatrix()); + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The original width of the image + /// The original height of the image + /// The placement matrix + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Stream jpgStream, int originalWidth, int originalHeight, Matrix matrix) + { + page.ContentStream + .SaveState() + .CTM(matrix) + .Paint(page.AddImageToResources(jpgStream, originalWidth, originalHeight)) + .RestoreState(); + + return page; + } + + /// + /// Add an image to the pdf page + /// + /// The page to add the image to + /// The image to add + /// The original width of the image + /// The original height of the image + /// The placement rectangle + /// The same to chain other calls. + public static PdfPage AddImage(this PdfPage page, Stream jpgStream, int originalWidth, int originalHeight, Rectangle rectangle) + => page.AddImage(jpgStream, originalWidth, originalHeight, rectangle.AsPlacementMatrix()); + + /// + /// Add shapes to the pdf page + /// + /// The page to add the shapes to + /// The action painting the shapes + /// The same to chain other calls. + public static PdfPage AddShapes(this PdfPage page, Action paintAction) + => page.AddShapes(paintAction, static (action, context) => action(context)); + + /// + /// Add shapes to the pdf page + /// + /// Type of + /// The page to add the shapes to + /// Data that can be passed to the + /// The action painting the shapes + /// The same to chain other calls. + public static PdfPage AddShapes(this PdfPage page, T data, Action paintAction) + { + using (var context = new ShapeContext(page.ContentStream, page.Resources)) + paintAction(data, context); + + return page; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/PrimitiveExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/PrimitiveExtensions.cs new file mode 100644 index 0000000..33a5ef1 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Extensions/PrimitiveExtensions.cs @@ -0,0 +1,24 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.Extensions +{ + /// + /// Extension methods for primitives + /// + public static class PrimitiveExtensions + { + /// + /// Convert a to a transformation matrix + /// + /// The to use + /// Returns a representing the provided . + public static Matrix AsPlacementMatrix(this Rectangle rectangle) + { + rectangle = rectangle.ConvertTo(Unit.Points); + return new Matrix( + rectangle.URX.Raw - rectangle.LLX.Raw, 0, + 0, rectangle.URY.Raw - rectangle.LLY.Raw, + rectangle.LLX.Raw, rectangle.LLY.Raw); + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/StreamExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/StreamExtensions.cs deleted file mode 100644 index d3fc496..0000000 --- a/src/Synercoding.FileFormats.Pdf/Extensions/StreamExtensions.cs +++ /dev/null @@ -1,265 +0,0 @@ -using Synercoding.FileFormats.Pdf.Helpers; -using Synercoding.FileFormats.Pdf.PdfInternals; -using Synercoding.FileFormats.Pdf.Primitives; -using Synercoding.Primitives; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; - -namespace Synercoding.FileFormats.Pdf.Extensions -{ - internal static class StreamExtensions - { - public static Stream IndirectStream(this Stream stream, PdfReference objectReference, Stream data, Action dictionaryAction, params StreamFilter[] streamFilters) - { - stream - ._startObject(objectReference) - .Dictionary(dictionary => - { - dictionary.Write("/Length", data.Length); - if(streamFilters != null && streamFilters.Length > 0) - { - if(streamFilters.Length == 1) - { - dictionary.Write("/Filter", streamFilters[0].ToPdfName()); - } - else - { - dictionary.Write("/Filter", streamPart => - { - streamPart - .WriteSingleByte(0x5B) // [ - .Space(); - - foreach(var filter in streamFilters) - { - streamPart - .Write(filter.ToPdfName()) - .Space(); - } - - streamPart - .WriteSingleByte(0x5D); // ] - }); - } - } - - dictionaryAction(dictionary); - }) - .Write("stream") - .NewLine() - ._copyFrom(data) - .NewLine() - .Write("endstream") - .NewLine() - ._endObject() - .NewLine(); - - return stream; - } - - public static Stream IndirectDictionary(this Stream stream, PdfReference objectReference, Action dictionaryAction) - { - stream - ._startObject(objectReference) - .Dictionary(dictionaryAction) - ._endObject() - .NewLine(); - - return stream; - } - - public static Stream Dictionary(this Stream stream, Action streamAction) - { - stream - ._startDictionary() - .NewLine(); - - streamAction(new PdfDictionary(stream)); - - stream - ._endDictionary() - .NewLine(); - - return stream; - } - - public static Stream WriteSingleByte(this Stream stream, byte value) - { - stream.WriteByte(value); - return stream; - } - - public static Stream Write(this Stream stream, Rectangle rectangle) - { - rectangle = rectangle.ConvertTo(Unit.Points); - return stream - .WriteSingleByte(0x5B) // [ - .Space() - .Write(rectangle.LLX.Raw) - .Space() - .Write(rectangle.LLY.Raw) - .Space() - .Write(rectangle.URX.Raw) - .Space() - .Write(rectangle.URY.Raw) - .Space() - .WriteSingleByte(0x5D); // ] - } - - public static Stream Write(this Stream stream, int value) - { - var intSize = ByteSizes.Size(value); - for (int i = intSize - 1; i >= 0; i--) - { - var result = (byte)( '0' + ( (int)( value / Math.Pow(10, i) ) % 10 ) ); - stream.WriteByte(result); - } - - return stream; - } - - public static Stream Write(this Stream stream, long value) - { - var intSize = ByteSizes.Size(value); - for (int i = intSize - 1; i >= 0; i--) - { - var result = (byte)( '0' + ( (int)( value / Math.Pow(10, i) ) % 10 ) ); - stream.WriteByte(result); - } - - return stream; - } - - public static Stream Write(this Stream stream, double value) - { - var stringValue = value.ToString("0.0########", CultureInfo.InvariantCulture); - var bytes = Encoding.ASCII.GetBytes(stringValue); - stream.Write(bytes, 0, bytes.Length); - - return stream; - } - - public static Stream Write(this Stream stream, string text) - { - var bytes = Encoding.ASCII.GetBytes(text); - stream.Write(bytes, 0, bytes.Length); - - return stream; - } - - public static Stream Write(this Stream stream, Matrix matrix) - { - stream - .Write(matrix.A) - .Space() - .Write(matrix.B) - .Space() - .Write(matrix.C) - .Space() - .Write(matrix.D) - .Space() - .Write(matrix.E) - .Space() - .Write(matrix.F) - .Space() - .Write("cm"); - - return stream; - } - - public static Stream Space(this Stream stream) - { - stream.WriteByte(0x20); // space - - return stream; - } - - public static Stream Write(this Stream stream, PdfReference objectReference) - { - return stream - .Write(objectReference.ObjectId) - .Space() - .Write(objectReference.Generation) - .Space() - .WriteSingleByte(0x52); // R - } - - public static Stream Write(this Stream stream, IEnumerable objectReferences) - { - stream.WriteByte(0x5B); // [ - - foreach(var objectReference in objectReferences) - { - stream.Write(objectReference); - stream.NewLine(); - } - - stream.WriteByte(0x5D); // ] - - return stream; - } - - public static Stream NewLine(this Stream stream) - { - stream.WriteByte(0x0D); - stream.WriteByte(0x0A); - - return stream; - } - - public static Stream EmptyDictionary(this Stream stream) - { - return stream._startDictionary().Space()._endDictionary(); - } - - private static Stream _startDictionary(this Stream stream) - { - stream.WriteByte(0x3C); - stream.WriteByte(0x3C); - - return stream; - } - - private static Stream _endDictionary(this Stream stream) - { - stream.WriteByte(0x3E); - stream.WriteByte(0x3E); - - return stream; - } - - private static Stream _startObject(this Stream stream, PdfReference objectReference) - { - return stream - .Write(objectReference.ObjectId) - .Space() - .Write(objectReference.Generation) - .Space() - .WriteSingleByte(0x6F) // o - .WriteSingleByte(0x62) // b - .WriteSingleByte(0x6A) // j - .NewLine(); - } - - private static Stream _endObject(this Stream stream) - { - stream.WriteByte(0x65); // e - stream.WriteByte(0x6E); // n - stream.WriteByte(0x64); // d - stream.WriteByte(0x6F); // o - stream.WriteByte(0x62); // b - stream.WriteByte(0x6A); // j - - return stream; - } - - private static Stream _copyFrom(this Stream stream, Stream data) - { - data.CopyTo(stream); - return stream; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/Extensions/StreamFilterExtensions.cs b/src/Synercoding.FileFormats.Pdf/Extensions/StreamFilterExtensions.cs deleted file mode 100644 index f7a526f..0000000 --- a/src/Synercoding.FileFormats.Pdf/Extensions/StreamFilterExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Synercoding.FileFormats.Pdf.PdfInternals; -using System; - -namespace Synercoding.FileFormats.Pdf.Extensions -{ - internal static class StreamFilterExtensions - { - public static string ToPdfName(this StreamFilter streamFilter) - { - return streamFilter switch - { - StreamFilter.DCTDecode => "/DCTDecode", - _ => throw new NotImplementedException(), - }; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/ISpanWriteable.cs b/src/Synercoding.FileFormats.Pdf/Helpers/ISpanWriteable.cs deleted file mode 100644 index 33230e1..0000000 --- a/src/Synercoding.FileFormats.Pdf/Helpers/ISpanWriteable.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Synercoding.FileFormats.Pdf.Helpers -{ - internal interface ISpanWriteable - { - void FillSpan(Span bytes); - int ByteSize(); - } -} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/IStreamWriteable.cs b/src/Synercoding.FileFormats.Pdf/Helpers/IStreamWriteable.cs deleted file mode 100644 index e78d0d1..0000000 --- a/src/Synercoding.FileFormats.Pdf/Helpers/IStreamWriteable.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; - -namespace Synercoding.FileFormats.Pdf.Helpers -{ - /// - /// Interface that enables writting implementor to a stream - /// - public interface IStreamWriteable - { - /// - /// Write the object to the provided stream - /// - /// The stream to write to - /// The position in the stream where is object is written - uint WriteToStream(Stream stream); - } -} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/MathHelper.cs b/src/Synercoding.FileFormats.Pdf/Helpers/MathHelper.cs deleted file mode 100644 index fe9e50c..0000000 --- a/src/Synercoding.FileFormats.Pdf/Helpers/MathHelper.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Synercoding.FileFormats.Pdf.Helpers -{ - internal static class MathHelper - { - public static double DegreeToRad(double degree) - { - return degree * Math.PI / 180; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/PdfTypeHelper.cs b/src/Synercoding.FileFormats.Pdf/Helpers/PdfTypeHelper.cs deleted file mode 100644 index 85fb603..0000000 --- a/src/Synercoding.FileFormats.Pdf/Helpers/PdfTypeHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; - -namespace Synercoding.FileFormats.Pdf.Helpers -{ - internal static class PdfTypeHelper - { - public static string ToPdfHexadecimalString(string input) - { - var bytes = System.Text.Encoding.ASCII.GetBytes(input); - var builder = new System.Text.StringBuilder(bytes.Length + 2); - builder.Append("<"); - foreach (var b in bytes) - { - builder.Append(b.ToString("X2")); - } - builder.Append(">"); - return builder.ToString(); - } - - public static string ToPdfDate(DateTimeOffset input) - { - var datePart = input.ToString("yyyyMMddHHmmss"); - - var builder = new System.Text.StringBuilder(22); - builder.Append("(D:"); - builder.Append(datePart); - - var hours = input.Offset.Hours; - var minutes = input.Offset.Minutes; - - if (hours == 0 && minutes == 0) - { - builder.Append("Z00'00"); - } - else - { - if (hours > 0 || ( hours == 0 && minutes > 0 )) - { - builder.Append("+"); - } - else - { - builder.Append("-"); - } - builder.Append(Math.Abs(hours).ToString().PadLeft(2, '0')); - builder.Append("'"); - builder.Append(minutes.ToString().PadLeft(2, '0')); - } - builder.Append(")"); - - return builder.ToString(); - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/IPath.cs b/src/Synercoding.FileFormats.Pdf/IPath.cs new file mode 100644 index 0000000..8dea653 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/IPath.cs @@ -0,0 +1,133 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf +{ + /// + /// Interface representing a path in the content stream of a page + /// + public interface IPath + { + /// + /// Begin a new subpath by moving the current point to the coordinates (, ), + /// ommitting any connecting line segment. Appends an (m) operator to the content stream + /// + /// The X coordinate of the move + /// The Y coordinate of the move + /// The calling to support chaining operations. + IPath Move(double x, double y); + + /// + /// Begin a new subpath by moving the current point to the coordinates (), + /// ommitting any connecting line segment. Appends an (m) operator to the content stream + /// + /// The point to where to move + /// The calling to support chaining operations. + IPath Move(Point point); + + /// + /// Add a line (l) operator to the content stream + /// + /// The X coordinate of the line end point + /// The Y coordinate of the line end point + /// The calling to support chaining operations. + IPath LineTo(double x, double y); + + /// + /// Add a line (l) operator to the content stream + /// + /// The point to where to line to + /// The calling to support chaining operations. + IPath LineTo(Point point); + + /// + /// Add a rectangle (re) operator to the content stream + /// + /// The X coordinate of the rectangle + /// The Y coordinate of the rectangle + /// The width of the rectangle + /// The height of the rectangle + /// The calling to support chaining operations. + IPath Rectangle(double x, double y, double width, double height); + + /// + /// Add a rectangle (re) operator to the content stream + /// + /// The rectangle to add to the content stream + /// The calling to support chaining operations. + IPath Rectangle(Rectangle rectangle); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (, ), + /// using (, ) and (, ) as the Bézier control points. + /// Adds a Cubic Bézier Curve (c) operator to the content stream. + /// + /// The X coordinate of the first control point + /// The Y coordinate of the first control point + /// The X coordinate of the second control point + /// The Y coordinate of the second control point + /// The X coordinate of the endpoint of the curve + /// The Y coordinate of the endpoint of the curve + /// The calling to support chaining operations. + IPath CurveTo(double cpX1, double cpY1, double cpX2, double cpY2, double finalX, double finalY); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (), + /// using () and () as the Bézier control points. + /// Adds a Cubic Bézier Curve (c) operator to the content stream. + /// + /// The first control point + /// The second control point + /// The endpoint of the curve + /// The calling to support chaining operations. + IPath CurveTo(Point cp1, Point cp2, Point final); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (, ), + /// using the current point and (, ) as the Bézier control points. + /// Adds a Cubic Bézier Curve (v) operator to the content stream. + /// + /// The X coordinate of the second control point + /// The Y coordinate of the second control point + /// The X coordinate of the endpoint of the curve + /// The Y coordinate of the endpoint of the curve + /// The calling to support chaining operations. + IPath CurveToWithStartAnker(double cpX2, double cpY2, double finalX, double finalY); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (), + /// using the current point and () as the Bézier control points. + /// Adds a Cubic Bézier Curve (v) operator to the content stream. + /// + /// The second control point + /// The endpoint of the curve + /// The calling to support chaining operations. + IPath CurveToWithStartAnker(Point cp2, Point final); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (, ), + /// using (, ) and (, ) as the Bézier control points. + /// Adds a Cubic Bézier Curve (y) operator to the content stream. + /// + /// The X coordinate of the first control point + /// The Y coordinate of the first control point + /// The X coordinate of the endpoint of the curve + /// The Y coordinate of the endpoint of the curve + /// The calling to support chaining operations. + IPath CurveToWithEndAnker(double cpX1, double cpY1, double finalX, double finalY); + + /// + /// Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (), + /// using () and () as the Bézier control points. + /// Adds a Cubic Bézier Curve (y) operator to the content stream. + /// + /// The first control point + /// The endpoint of the curve + /// The calling to support chaining operations. + IPath CurveToWithEndAnker(Point cp1, Point final); + + /// + /// Close the + /// + void Close(); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/IShapeContext.cs b/src/Synercoding.FileFormats.Pdf/IShapeContext.cs new file mode 100644 index 0000000..ad72109 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/IShapeContext.cs @@ -0,0 +1,31 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics; +using System; + +namespace Synercoding.FileFormats.Pdf +{ + /// + /// Interface representing a context which can be used to draw shapes on a page + /// + public interface IShapeContext + { + /// + /// Change the default graphics state + /// + /// Action used to configure the + /// The calling to support chaining + IShapeContext DefaultState(Action configureState); + + /// + /// Start a new + /// + /// The new object to chain pathing operators + IPath NewPath(); + + /// + /// Start a new with a different graphics state + /// + /// The action used to change the + /// The new object to chain pathing operators + IPath NewPath(Action configureState); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Image.cs b/src/Synercoding.FileFormats.Pdf/Image.cs new file mode 100644 index 0000000..24ddae0 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Image.cs @@ -0,0 +1,88 @@ +using SixLabors.ImageSharp; +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using System; +using System.IO; + +namespace Synercoding.FileFormats.Pdf +{ + /// + /// Class representing an image inside a pdf + /// + public sealed class Image : IPdfObject, IDisposable + { + private readonly Stream _imageStream; + private readonly int _width; + private readonly int _height; + + private bool _disposed; + private bool _isWritten; + + internal Image(PdfReference id, SixLabors.ImageSharp.Image image) + { + Reference = id; + + var ms = new MemoryStream(); + image.SaveAsJpeg(ms, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder() + { + Quality = 100, + Subsample = SixLabors.ImageSharp.Formats.Jpeg.JpegSubsample.Ratio420 + }); + _width = image.Width; + _height = image.Height; + ms.Position = 0; + _imageStream = ms; + } + + internal Image(PdfReference id, Stream jpgStream, int width, int height) + { + Reference = id; + + _width = width; + _height = height; + _imageStream = jpgStream; + } + + /// + public PdfReference Reference { get; private set; } + + /// + public void Dispose() + { + if (!_disposed) + { + _imageStream.Dispose(); + _disposed = true; + } + } + + internal bool TryWriteToStream(PdfStream stream, out uint position) + { + position = 0; + + if (_isWritten) + return false; + if (_disposed) + throw new ObjectDisposedException(nameof(_imageStream), "Internal image is already disposed"); + + position = (uint)stream.Position; + + stream.IndirectStream(this, _imageStream, this, static (image, dictionary) => + { + dictionary + .Type(ObjectType.XObject) + .SubType(XObjectSubType.Image) + .Write(PdfName.Get("Width"), image._width) + .Write(PdfName.Get("Height"), image._height) + .Write(PdfName.Get("ColorSpace"), PdfName.Get("DeviceRGB")) + .Write(PdfName.Get("BitsPerComponent"), 8) + .Write(PdfName.Get("Decode"), 0f, 1f, 0f, 1f, 0f, 1f); + }, + StreamFilter.DCTDecode); + + _isWritten = true; + + return true; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/ByteSizes.cs b/src/Synercoding.FileFormats.Pdf/Internals/ByteSizes.cs similarity index 92% rename from src/Synercoding.FileFormats.Pdf/Helpers/ByteSizes.cs rename to src/Synercoding.FileFormats.Pdf/Internals/ByteSizes.cs index 44c2934..e59a3d3 100644 --- a/src/Synercoding.FileFormats.Pdf/Helpers/ByteSizes.cs +++ b/src/Synercoding.FileFormats.Pdf/Internals/ByteSizes.cs @@ -1,7 +1,7 @@ using System; using System.Globalization; -namespace Synercoding.FileFormats.Pdf.Helpers +namespace Synercoding.FileFormats.Pdf.Internals { internal static class ByteSizes { diff --git a/src/Synercoding.FileFormats.Pdf/Internals/Map.cs b/src/Synercoding.FileFormats.Pdf/Internals/Map.cs new file mode 100644 index 0000000..b37e1f2 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Internals/Map.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Synercoding.FileFormats.Pdf.Internals +{ + internal sealed class Map : IEnumerable> + where T1 : notnull + where T2 : notnull + { + private readonly IDictionary _forward = new Dictionary(); + private readonly IDictionary _reverse = new Dictionary(); + + public Map() + { + Forward = new Indexer(_forward); + Reverse = new Indexer(_reverse); + } + + public int Count => _forward.Count; + + public void Add(T1 t1, T2 t2) + { + _forward.Add(t1, t2); + _reverse.Add(t2, t1); + } + + public IEnumerator> GetEnumerator() + { + return _forward.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public Indexer Forward { get; } + public Indexer Reverse { get; } + + public sealed class Indexer : IReadOnlyDictionary + where T3 : notnull + where T4 : notnull + { + private readonly IDictionary _dictionary; + + public Indexer(IDictionary dictionary) + { + _dictionary = dictionary; + } + + public T4 this[T3 index] + { + get => _dictionary[index]; + set => _dictionary[index] = value; + } + + public IEnumerable Keys => _dictionary.Keys; + + public IEnumerable Values => _dictionary.Values; + + public int Count => _dictionary.Count; + + public bool Contains(T3 value) + => _dictionary.ContainsKey(value); + + public bool ContainsKey(T3 key) + => _dictionary.ContainsKey(key); + + public IEnumerator> GetEnumerator() + => _dictionary.GetEnumerator(); + + public bool TryGetValue(T3 key, out T4 value) + => throw new InvalidOperationException("TryGetValue is not supported on types with non-nullable values."); + + IEnumerator IEnumerable.GetEnumerator() + => _dictionary.GetEnumerator(); + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Internals/Path.cs b/src/Synercoding.FileFormats.Pdf/Internals/Path.cs new file mode 100644 index 0000000..1c152da --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Internals/Path.cs @@ -0,0 +1,174 @@ +using Synercoding.FileFormats.Pdf.LowLevel; +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.Pathing.Construction; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.State; +using Synercoding.Primitives; +using System; + +namespace Synercoding.FileFormats.Pdf.Internals +{ + internal class Path : IPath + { + private readonly ContentStream _contentStream; + + public Path(ContentStream contentStream, GraphicsState graphicsState) + { + _contentStream = contentStream; + GraphicsState = graphicsState; + + _startPath(); + } + + internal GraphicsState GraphicsState { get; } + + private void _startPath() + { + _contentStream.SaveState(); + + _contentStream + .Write(new LineWidthOperator(GraphicsState.LineWidth)) + .Write(new LineCapOperator(GraphicsState.LineCap)) + .Write(new LineJoinOperator(GraphicsState.LineJoin)) + .Write(new MiterLimitOperator(GraphicsState.MiterLimit)) + .Write(new DashOperator(GraphicsState.Dash.Array, GraphicsState.Dash.Phase)); + } + + 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.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.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.Write(new StrokeOperator()); + } + else + { + _contentStream.Write(new EndPathOperator()); + } + + _contentStream.RestoreState(); + } + + public void Close() + { + _contentStream.Write(new CloseOperator()); + } + + public IPath CurveTo(double cpX1, double cpY1, double cpX2, double cpY2, double finalX, double finalY) + { + _contentStream.Write(new CubicBezierCurveDualControlPointsOperator(cpX1, cpY1, cpX2, cpY2, finalX, finalY)); + return this; + } + + public IPath CurveTo(Point cp1, Point cp2, Point final) + { + _contentStream.Write(new CubicBezierCurveDualControlPointsOperator(cp1, cp2, final)); + return this; + } + + public IPath CurveToWithEndAnker(double cpX1, double cpY1, double finalX, double finalY) + { + _contentStream.Write(new CubicBezierCurveFinalControlPointsOperator(cpX1, cpY1, finalX, finalY)); + return this; + } + + public IPath CurveToWithEndAnker(Point cp1, Point final) + { + _contentStream.Write(new CubicBezierCurveFinalControlPointsOperator(cp1, final)); + return this; + } + + public IPath CurveToWithStartAnker(double cpX2, double cpY2, double finalX, double finalY) + { + _contentStream.Write(new CubicBezierCurveInitialControlPointsOperator(cpX2, cpY2, finalX, finalY)); + return this; + } + + public IPath CurveToWithStartAnker(Point cp2, Point final) + { + _contentStream.Write(new CubicBezierCurveInitialControlPointsOperator(cp2, final)); + return this; + } + + public IPath LineTo(double x, double y) + { + _contentStream.Write(new LineOperator(x, y)); + return this; + } + + public IPath LineTo(Point point) + { + _contentStream.Write(new LineOperator(point)); + return this; + } + + public IPath Move(double x, double y) + { + _contentStream.Write(new MoveOperator(x, y)); + return this; + } + + public IPath Move(Point point) + { + _contentStream.Write(new MoveOperator(point)); + return this; + } + + public IPath Rectangle(double x, double y, double width, double height) + { + _contentStream.Write(new RectangleOperator(x, y, width, height)); + return this; + } + + public IPath Rectangle(Rectangle rectangle) + { + _contentStream.Write(new RectangleOperator(rectangle)); + return this; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Internals/ShapeContext.cs b/src/Synercoding.FileFormats.Pdf/Internals/ShapeContext.cs new file mode 100644 index 0000000..aabbe92 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/Internals/ShapeContext.cs @@ -0,0 +1,63 @@ +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.FileFormats.Pdf.LowLevel.Graphics; +using System; + +namespace Synercoding.FileFormats.Pdf.Internals +{ + internal sealed class ShapeContext : IShapeContext, IDisposable + { + private readonly ContentStream _contentStream; + + internal GraphicsState State { get; } + + internal Path? CurrentPath { get; set; } = null; + + internal ShapeContext(ContentStream contentStream, PageResources resources) + { + _contentStream = contentStream; + + _contentStream.SaveState(); + + State = new GraphicsState(resources); + } + + public void Dispose() + { + _finishPathIfNeeded(); + + _contentStream.RestoreState(); + } + + public IShapeContext DefaultState(Action configureState) + { + configureState(State); + + return this; + } + public IPath NewPath() + => NewPath(_ => { }); + + public IPath NewPath(Action configureState) + { + _finishPathIfNeeded(); + + var state = State.Clone(); + configureState(state); + + var path = new Path(_contentStream, state); + + CurrentPath = path; + + return path; + } + + private void _finishPathIfNeeded() + { + if (CurrentPath != null) + { + CurrentPath.FinishPath(); + CurrentPath = null; + } + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/SpanHelper.cs b/src/Synercoding.FileFormats.Pdf/Internals/SpanHelper.cs similarity index 76% rename from src/Synercoding.FileFormats.Pdf/Helpers/SpanHelper.cs rename to src/Synercoding.FileFormats.Pdf/Internals/SpanHelper.cs index a6cf7a3..79e4d74 100644 --- a/src/Synercoding.FileFormats.Pdf/Helpers/SpanHelper.cs +++ b/src/Synercoding.FileFormats.Pdf/Internals/SpanHelper.cs @@ -1,7 +1,7 @@ using System; -namespace Synercoding.FileFormats.Pdf.Helpers -{ +namespace Synercoding.FileFormats.Pdf.Internals +{ internal static class SpanHelper { public static int FillSpan(Span span, int integer) @@ -10,7 +10,7 @@ public static int FillSpan(Span span, int integer) var intSize = ByteSizes.Size(integer); for (int i = intSize - 1; i >= 0; i--) { - span[i] = (byte)( '0' + ( val % 10 ) ); + span[i] = (byte)('0' + (val % 10)); val = val / 10; } return intSize; @@ -22,7 +22,7 @@ public static int FillSpan(Span span, uint integer) var intSize = ByteSizes.Size(integer); for (int i = intSize - 1; i >= 0; i--) { - span[i] = (byte)( '0' + ( val % 10 ) ); + span[i] = (byte)('0' + (val % 10)); val = val / 10; } return intSize; @@ -42,13 +42,5 @@ public static int WriteIndirectObjectRef(Span bytes, int objectRef, int ge return position; } - - public static int WriteNewLine(Span bytes) - { - bytes[0] = 0x0D; // CR - bytes[1] = 0x0A; // LF - - return 2; - } } } diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Catalog.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Catalog.cs new file mode 100644 index 0000000..eb35d05 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Catalog.cs @@ -0,0 +1,39 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + internal class Catalog : IPdfObject + { + private bool _isWritten; + + public Catalog(PdfReference id, PageTree pageTree) + { + Reference = id; + PageTree = pageTree; + } + + /// + public PdfReference Reference { get; } + + public PageTree PageTree { get; } + + internal uint WriteToStream(PdfStream stream) + { + if (_isWritten) + throw new InvalidOperationException("Object is already written to stream."); + + var position = (uint)stream.Position; + + stream.IndirectDictionary(this, static (catalog, dictionary) => + { + dictionary + .Type(ObjectType.Catalog) + .Write(PdfName.Get("Pages"), catalog.PageTree.Reference); + }); + _isWritten = true; + + return position; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs new file mode 100644 index 0000000..9d3187e --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/ContentStream.cs @@ -0,0 +1,556 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.Color; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.State; +using System; +using System.IO; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// Class to represent a content stream + /// + 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) + { + Reference = id; + _streamWrapper = pdfStream; + } + + /// + public PdfReference Reference { get; } + + internal bool IsWritten { get; private set; } + + /// + public void Dispose() + { + _streamWrapper.Dispose(); + } + + /// + /// Write a save state operator (q) to the stream + /// + /// The to support chaining operations. + public ContentStream SaveState() + { + _streamWrapper.Write('q').NewLine(); + + return this; + } + + /// + /// Write a restore state operator (Q) to the stream + /// + /// The to support chaining operations. + public ContentStream RestoreState() + { + _streamWrapper.Write('Q').NewLine(); + + return this; + } + + /// + /// Write a transformation matrix operator (cm) to the stream + /// + /// The to write. + /// The to support chaining operations. + public ContentStream CTM(Matrix matrix) + { + _streamWrapper + .Write(matrix.A) + .Space() + .Write(matrix.B) + .Space() + .Write(matrix.C) + .Space() + .Write(matrix.D) + .Space() + .Write(matrix.E) + .Space() + .Write(matrix.F) + .Space() + .Write("cm") + .NewLine(); + + return this; + } + + /// + /// Write a xobject paint operator (Do) to the stream + /// + /// The of the xobject to write. + /// The to support chaining operations. + public ContentStream Paint(PdfName resource) + { + _streamWrapper.Write(resource).Space().Write("Do").NewLine(); + + return this; + } + + /// + /// Write the operator (m) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(MoveOperator op) + { + _streamWrapper + .Write(op.X).Space().Write(op.Y).Space() + .Write('m').NewLine(); + + return this; + } + + /// + /// Write the operator (l) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(LineOperator op) + { + _streamWrapper + .Write(op.X).Space().Write(op.Y).Space() + .Write('l').NewLine(); + + return this; + } + + /// + /// Write the operator (c) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CubicBezierCurveDualControlPointsOperator op) + { + _streamWrapper + .Write(op.X1).Space().Write(op.Y1).Space() + .Write(op.X2).Space().Write(op.Y2).Space() + .Write(op.X3).Space().Write(op.Y3).Space() + .Write('c').NewLine(); + + return this; + } + + /// + /// Write the operator (v) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CubicBezierCurveInitialControlPointsOperator op) + { + _streamWrapper + .Write(op.X2).Space().Write(op.Y2).Space() + .Write(op.X3).Space().Write(op.Y3).Space() + .Write('v').NewLine(); + + return this; + } + + /// + /// Write the operator (y) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CubicBezierCurveFinalControlPointsOperator op) + { + _streamWrapper + .Write(op.X1).Space().Write(op.Y1).Space() + .Write(op.X3).Space().Write(op.Y3).Space() + .Write('y').NewLine(); + + return this; + } + + /// + /// Write the operator (re) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(RectangleOperator op) + { + _streamWrapper + .Write(op.X).Space().Write(op.Y).Space() + .Write(op.Width).Space().Write(op.Height).Space() + .Write("re").NewLine(); + + return this; + } + + /// + /// Write the operator (h) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CloseOperator op) + { + _ = op; + + _streamWrapper + .Write('h').NewLine(); + + return this; + } + + /// + /// Write the operator (S) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(StrokeOperator op) + { + _ = op; + + _streamWrapper + .Write('S').NewLine(); + + return this; + } + + /// + /// Write the operator (s) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CloseAndStrokeOperator op) + { + _ = op; + + _streamWrapper + .Write('s').NewLine(); + + return this; + } + + /// + /// Write the operator (f or f*) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(FillOperator op) + { + _ = op.FillRule switch + { + FillRule.NonZeroWindingNumber => _streamWrapper.Write('f').NewLine(), + FillRule.EvenOdd => _streamWrapper.Write('f').Write('*').NewLine(), + _ => throw new InvalidOperationException(UNKNOWN_FILL_RULE) + }; + + return this; + } + + /// + /// Write the operator (B or B*) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(FillAndStrokeOperator op) + { + _ = op.FillRule switch + { + FillRule.NonZeroWindingNumber => _streamWrapper.Write('B').NewLine(), + FillRule.EvenOdd => _streamWrapper.Write('B').Write('*').NewLine(), + _ => throw new InvalidOperationException(UNKNOWN_FILL_RULE) + }; + + return this; + } + + /// + /// Write the operator (b or b*) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CloseFillAndStrokeOperator op) + { + _ = op.FillRule switch + { + FillRule.NonZeroWindingNumber => _streamWrapper.Write('b').NewLine(), + FillRule.EvenOdd => _streamWrapper.Write('b').Write('*').NewLine(), + _ => throw new InvalidOperationException(UNKNOWN_FILL_RULE) + }; + + return this; + } + + /// + /// Write the operator (n) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(EndPathOperator op) + { + _ = op; + + _streamWrapper + .Write('n').NewLine(); + + return this; + } + + /// + /// Write the operator (S) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(StrokingColorSpaceOperator op) + { + _streamWrapper + .Write(op.ColorSpace) + .Space() + .Write('C').Write('S') + .NewLine(); + + return this; + } + + /// + /// Write the operator (s) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(NonStrokingColorSpaceOperator op) + { + _streamWrapper + .Write(op.ColorSpace) + .Space() + .Write('c').Write('s') + .NewLine(); + + return this; + } + + /// + /// Write the operator (G) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(GrayStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Gray) + .Space() + .Write('G') + .NewLine(); + + return this; + } + + /// + /// Write the operator (g) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(GrayNonStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Gray) + .Space() + .Write('g') + .NewLine(); + + return this; + } + + /// + /// Write the operator (RG) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(RgbStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Red) + .Space() + .Write(op.Color.Green) + .Space() + .Write(op.Color.Blue) + .Space() + .Write('R') + .Write('G') + .NewLine(); + + return this; + } + + /// + /// Write the operator (rg) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(RgbNonStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Red) + .Space() + .Write(op.Color.Green) + .Space() + .Write(op.Color.Blue) + .Space() + .Write('r') + .Write('g') + .NewLine(); + + return this; + } + + /// + /// Write the operator (K) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CmykStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Cyan) + .Space() + .Write(op.Color.Magenta) + .Space() + .Write(op.Color.Yellow) + .Space() + .Write(op.Color.Key) + .Space() + .Write('K') + .NewLine(); + + return this; + } + + /// + /// Write the operator (k) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(CmykNonStrokingColorOperator op) + { + _streamWrapper + .Write(op.Color.Cyan) + .Space() + .Write(op.Color.Magenta) + .Space() + .Write(op.Color.Yellow) + .Space() + .Write(op.Color.Key) + .Space() + .Write('k') + .NewLine(); + + return this; + } + + /// + /// Write the operator (w) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(LineWidthOperator op) + { + _streamWrapper + .Write(op.Width) + .Space() + .Write('w') + .NewLine(); + + return this; + } + + /// + /// Write the operator (J) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(LineCapOperator op) + { + _streamWrapper + .Write((int)op.Style) + .Space() + .Write('J') + .NewLine(); + + return this; + } + + /// + /// Write the operator (j) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(LineJoinOperator op) + { + _streamWrapper + .Write((int)op.Style) + .Space() + .Write('j') + .NewLine(); + + return this; + } + + /// + /// Write the operator (M) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(MiterLimitOperator op) + { + _streamWrapper + .Write(op.Limit) + .Space() + .Write('M') + .NewLine(); + + return this; + } + + /// + /// Write the operator (d) to the stream + /// + /// The operator to write + /// The to support chaining operations. + public ContentStream Write(DashOperator op) + { + _streamWrapper + .Write(op.Array) + .Space() + .Write(op.Phase) + .Space() + .Write('d') + .NewLine(); + + return this; + } + + internal uint WriteToStream(PdfStream stream) + { + if (IsWritten) + { + throw new InvalidOperationException("Object is already written to stream."); + } + var position = (uint)stream.Position; + + _streamWrapper.InnerStream.Position = 0; + stream.IndirectStream(this, _streamWrapper.InnerStream); + IsWritten = true; + + return position; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs new file mode 100644 index 0000000..5b5b622 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamArrayExtensions.cs @@ -0,0 +1,762 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Extensions +{ + /// + /// Extensions methods for + /// + public static class PdfStreamArrayExtensions + { + private const byte BRACKET_OPEN = 0x5B; // [ + private const byte BRACKET_CLOSE = 0x5D; // ] + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The array of doubles to write + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, double[] array) + { + stream + .WriteByte(BRACKET_OPEN) + .Space(); + + foreach (var number in array) + stream.Write(number).Space(); + + stream.WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, double number1, double number2) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, double number1, double number2, double number3) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, double number1, double number2, double number3, double number4) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth 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) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// The sixth 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) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// 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, + double number7) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .Write(number7) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// The sixth number in the array + /// 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, + double number8) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .Write(number7) + .Space() + .Write(number8) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The array of numbers to write + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, int[] array) + { + stream + .WriteByte(BRACKET_OPEN) + .Space(); + + foreach (var number in array) + stream.Write(number).Space(); + + stream.WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, int number1, int number2) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, int number1, int number2, int number3) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, int number1, int number2, int number3, int number4) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth 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) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// The sixth 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) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// 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, + int number7) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .Write(number7) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of numbers to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first number in the array + /// The second number in the array + /// The third number in the array + /// The fourth number in the array + /// The fifth number in the array + /// The sixth number in the array + /// 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, + int number8) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(number1) + .Space() + .Write(number2) + .Space() + .Write(number3) + .Space() + .Write(number4) + .Space() + .Write(number5) + .Space() + .Write(number6) + .Space() + .Write(number7) + .Space() + .Write(number8) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The array of pdfreferences to write + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, PdfReference[] objectReferences) + { + stream.WriteByte(BRACKET_OPEN).Space(); + + foreach (var objectReference in objectReferences) + { + stream.Write(objectReference); + stream.Space(); + } + + stream.WriteByte(BRACKET_CLOSE).NewLine(); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, PdfReference reference1, PdfReference reference2) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The third reference in the array + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, PdfReference reference1, PdfReference reference2, PdfReference reference3) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// 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, + PdfReference reference4) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .Write(reference4) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The third reference in the array + /// 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, + PdfReference reference3, + PdfReference reference4, + PdfReference reference5) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .Write(reference4) + .Space() + .Write(reference5) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The third reference in the array + /// The fourth reference in the array + /// 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, + PdfReference reference6) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .Write(reference4) + .Space() + .Write(reference5) + .Space() + .Write(reference6) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The third reference in the array + /// The fourth reference in the array + /// The fifth reference in the array + /// 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, + PdfReference reference7) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .Write(reference4) + .Space() + .Write(reference5) + .Space() + .Write(reference6) + .Space() + .Write(reference7) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + + /// + /// Write an array of pdf references to the pdf stream + /// + /// The pdf stream to write the array to. + /// The first reference in the array + /// The second reference in the array + /// The third reference in the array + /// The fourth reference in the array + /// The fifth reference in the array + /// The sixth reference in the array + /// 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, + PdfReference reference8) + { + stream + .WriteByte(BRACKET_OPEN) + .Space() + .Write(reference1) + .Space() + .Write(reference2) + .Space() + .Write(reference3) + .Space() + .Write(reference4) + .Space() + .Write(reference5) + .Space() + .Write(reference6) + .Space() + .Write(reference7) + .Space() + .Write(reference8) + .Space() + .WriteByte(BRACKET_CLOSE); + + return stream; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs new file mode 100644 index 0000000..67be0d7 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/PdfStreamExtensions.cs @@ -0,0 +1,215 @@ +using Synercoding.Primitives; +using System; +using System.IO; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Extensions +{ + /// + /// Extension method for + /// + public static class PdfStreamExtensions + { + /// + /// Write a space ' ' character to the stream. + /// + /// The to write to. + /// The to support chaining operations. + public static PdfStream Space(this PdfStream stream) + => stream.Write(' '); + + /// + /// Write a \r\n newline to the stream. + /// + /// The to write to. + /// The to support chaining operations. + public static PdfStream NewLine(this PdfStream stream) + => stream.Write('\r').Write('\n'); + + /// + /// Write a to the stream + /// + /// The to write to. + /// The pdf name to write. + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, PdfName name) + => stream.Write(name.ToString()); + + /// + /// Write a to the stream as a dictionary using + /// + /// The type with data to write to the stream + /// The stream to write the data to + /// The data to write + /// The instructions for writing the type + /// The to support chaining operations. + public static PdfStream IndirectDictionary(this PdfStream stream, TPdfObject pdfObject, Action dictionaryAction) + where TPdfObject : IPdfObject + { + return stream + .StartObject(pdfObject.Reference) + .Dictionary(pdfObject, dictionaryAction) + .EndObject() + .NewLine(); + } + + /// + /// Write a dictionary to the + /// + /// Type of data to use in the + /// The stream to write to + /// The data to use in the + /// Action to fill the dictionary + /// The to support chaining operations. + public static PdfStream Dictionary(this PdfStream stream, T data, Action streamAction) + { + stream + .Write('<') + .Write('<') + .NewLine(); + + streamAction(data, new PdfDictionary(stream)); + + stream + .Write('>') + .Write('>') + .NewLine(); + + return stream; + } + + /// + /// Write a dictionary to the + /// + /// The stream to write to + /// Action to fill the dictionary + /// The to support chaining operations. + public static PdfStream Dictionary(this PdfStream stream, Action streamAction) + => stream.Dictionary(streamAction, static (action, dict) => action(dict)); + + /// + /// Write an empty dictionary to the + /// + /// The stream to write to + /// The to support chaining operations. + public static PdfStream EmptyDictionary(this PdfStream stream) + => stream.Dictionary(_ => { }); + + /// + /// Write an object reference to the stream + /// + /// The stream to write to + /// The to write + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, PdfReference objectReference) + { + return stream + .Write(objectReference.ObjectId) + .Space() + .Write(objectReference.Generation) + .Space() + .Write('R'); + } + + /// + /// Write a rectangle to the stream as an array of [ LLX LLY URX URY ] + /// + /// The stream to write to + /// The to write + /// The to support chaining operations. + public static PdfStream Write(this PdfStream stream, Rectangle rectangle) + { + rectangle = rectangle.ConvertTo(Unit.Points); + + return stream.Write(rectangle.LLX.Raw, rectangle.LLY.Raw, rectangle.URX.Raw, rectangle.URY.Raw); + } + + internal static PdfStream StartObject(this PdfStream stream, PdfReference objectReference) + { + return stream + .Write(objectReference.ObjectId) + .Space() + .Write(objectReference.Generation) + .Space() + .Write('o') + .Write('b') + .Write('j') + .NewLine(); + } + + internal static PdfStream EndObject(this PdfStream stream) + { + return stream + .Write('e') + .Write('n') + .Write('d') + .Write('o') + .Write('b') + .Write('j') + .NewLine(); + } + + internal static PdfStream IndirectStream(this PdfStream contentStream, TPdfObject pdfObject, Stream stream, params StreamFilter[] streamFilters) + where TPdfObject : IPdfObject + => contentStream.IndirectStream(pdfObject, stream, _ => { }, streamFilters); + + internal static PdfStream IndirectStream(this PdfStream contentStream, TPdfObject pdfObject, Stream stream, Action dictionaryAction, params StreamFilter[] streamFilters) + where TPdfObject : IPdfObject + => contentStream.IndirectStream(pdfObject, stream, dictionaryAction, static (action, dict) => action(dict), streamFilters); + + internal static PdfStream IndirectStream(this PdfStream contentStream, TPdfObject pdfObject, Stream stream, TData data, Action dictionaryAction, params StreamFilter[] streamFilters) + where TPdfObject : IPdfObject + where TData : notnull + { + contentStream + .StartObject(pdfObject.Reference) + .Dictionary((stream, data, dictionaryAction, streamFilters), static (tuple, dictionary) => + { + dictionary.Write(PdfName.Get("Length"), tuple.stream.Length); + if (tuple.streamFilters != null && tuple.streamFilters.Length > 0) + { + if (tuple.streamFilters.Length == 1) + { + dictionary.Write(PdfName.Get("Filter"), tuple.streamFilters[0].ToPdfName()); + } + else + { + dictionary.Write(PdfName.Get("Filter"), tuple.streamFilters, static (filters, streamPart) => + { + streamPart + .WriteByte(0x5B) // [ + .Space(); + + foreach (var filter in filters) + { + streamPart + .Write(filter.ToPdfName()) + .Space(); + } + + streamPart + .WriteByte(0x5D); // ] + }); + } + } + + tuple.dictionaryAction(tuple.data, dictionary); + }) + .Write("stream") + .NewLine() + .CopyFrom(stream) + .NewLine() + .Write("endstream") + .NewLine() + .EndObject() + .NewLine(); + + return contentStream; + } + + internal static PdfStream CopyFrom(this PdfStream stream, Stream data) + { + data.CopyTo(stream.InnerStream); + return stream; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/StreamFilterExtensions.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/StreamFilterExtensions.cs new file mode 100644 index 0000000..b667e88 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Extensions/StreamFilterExtensions.cs @@ -0,0 +1,16 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Extensions +{ + internal static class StreamFilterExtensions + { + public static PdfName ToPdfName(this StreamFilter streamFilter) + { + return streamFilter switch + { + StreamFilter.DCTDecode => PdfName.Get("DCTDecode"), + _ => throw new NotImplementedException(), + }; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs new file mode 100644 index 0000000..f6b06e2 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/CmykColor.cs @@ -0,0 +1,124 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors +{ + /// + /// Class representing a CMYK color + /// + public sealed class CmykColor : Color, IEquatable + { + private const string COLOR_COMPONENT_OUT_OF_RANGE = "Color component value must be between 0.0 (zero concentration) and 1.0 (maximum concentration)."; + + private readonly double _cyan = 0; + private readonly double _magenta = 0; + private readonly double _yellow = 0; + private readonly double _key = 0; + + /// + /// Constructor for a + /// + public CmykColor() + { } + + /// + /// Constructor for a + /// + /// The cyan component of the color + /// The magenta component of the color + /// The yellow component of the color + /// The key component of the color + public CmykColor(double cyan, double magenta, double yellow, double key) + { + Cyan = cyan; + Magenta = magenta; + Yellow = yellow; + Key = key; + } + + /// + /// The cyan component of this color + /// + /// Value must be between 0.0 (zero concentration) and 1.0 (maximum concentration). + public double Cyan + { + get => _cyan; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Cyan), COLOR_COMPONENT_OUT_OF_RANGE); + + _cyan = value; + } + } + + /// + /// The magenta component of this color + /// + /// Value must be between 0.0 (zero concentration) and 1.0 (maximum concentration). + public double Magenta + { + get => _magenta; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Magenta), COLOR_COMPONENT_OUT_OF_RANGE); + + _magenta = value; + } + } + + /// + /// The yellow component of this color + /// + /// Value must be between 0.0 (zero concentration) and 1.0 (maximum concentration). + public double Yellow + { + get => _yellow; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Yellow), COLOR_COMPONENT_OUT_OF_RANGE); + + _yellow = value; + } + } + + /// + /// The key component of this color + /// + /// Value must be between 0.0 (zero concentration) and 1.0 (maximum concentration). + public double Key + { + get => _key; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Key), COLOR_COMPONENT_OUT_OF_RANGE); + + _key = value; + } + } + + /// + public bool Equals(CmykColor? other) + { + return other is not null + && Cyan == other.Cyan + && Magenta == other.Magenta + && Yellow == other.Yellow + && Key == other.Key; + } + + /// + public override bool Equals(Color? other) + => other is CmykColor color && Equals(color); + + /// + public override bool Equals(object? obj) + => obj is CmykColor color && Equals(color); + + /// + public override int GetHashCode() + => HashCode.Combine(Cyan, Magenta, Yellow, Key); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs new file mode 100644 index 0000000..a87d4a3 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/Color.cs @@ -0,0 +1,37 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors +{ + /// + /// Base class of all s. + /// + public abstract class Color : IEquatable + { + /// + public abstract bool Equals(Color? other); + + /// + public abstract override bool Equals(object? obj); + + /// + public abstract override int GetHashCode(); + + /// + /// Indicates whether the left Color is equal to the right Color. + /// + /// The on the left side of the == + /// The on the right side of the == + /// true if the left Color is equal to the right; otherwise, false. + public static bool operator ==(Color left, Color right) + => left.Equals(right); + + /// + /// Indicates whether the left Color is not equal to the right Color. + /// + /// The on the left side of the != + /// The on the right side of the != + /// true if the left Color is not equal to the right; otherwise, false. + public static bool operator !=(Color left, Color right) + => !( left == right ); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs new file mode 100644 index 0000000..432fd7a --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/GrayColor.cs @@ -0,0 +1,59 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors +{ + /// + /// Class representing a gray color + /// + public sealed class GrayColor : Color, IEquatable + { + /// + /// Construct a new object + /// + /// + public GrayColor(double gray) + { + Gray = gray; + } + + private readonly double _gray = 0; + + /// + /// The single component that represents the gray color. + /// + /// + /// 0.0 = Black + /// 1.0 = white + /// + public double Gray + { + get => _gray; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Gray), "Gray value must be between 0.0 (black) and 1.0 (white)."); + + _gray = value; + } + } + + /// + public bool Equals(GrayColor? other) + { + return other is not null + && Gray == other.Gray; + } + + /// + public override bool Equals(Color? other) + => other is GrayColor color && Equals(color); + + /// + public override bool Equals(object? obj) + => obj is GrayColor color && Equals(color); + + /// + public override int GetHashCode() + => HashCode.Combine(Gray); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/PredefinedColors.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/PredefinedColors.cs new file mode 100644 index 0000000..21a0842 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/PredefinedColors.cs @@ -0,0 +1,63 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors +{ + /// + /// Class with predefined colors + /// + public static class PredefinedColors + { + /// + /// Cyan + /// + public static Color Cyan { get; } = new CmykColor(1, 0, 0, 0); + + /// + /// Magenta + /// + public static Color Magenta { get; } = new CmykColor(0, 1, 0, 0); + + /// + /// Yellow + /// + public static Color Yellow { get; } = new CmykColor(0, 0, 1, 0); + + /// + /// Black + /// + public static Color Black { get; } = new GrayColor(0); + + /// + /// Dark gray + /// + public static Color DarkGray { get; } = new GrayColor(0.25); + + /// + /// Gray + /// + public static Color Gray { get; } = new GrayColor(0.5); + + /// + /// Light gray + /// + public static Color LightGray { get; } = new GrayColor(0.75); + + /// + /// White + /// + public static Color White { get; } = new GrayColor(1); + + /// + /// Red + /// + public static Color Red { get; } = new RgbColor(1, 0, 0); + + /// + /// Green + /// + public static Color Green { get; } = new RgbColor(0, 1, 0); + + /// + /// Blue + /// + public static Color Blue { get; } = new RgbColor(0, 0, 1); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs new file mode 100644 index 0000000..e4fa003 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Colors/RgbColor.cs @@ -0,0 +1,113 @@ +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors +{ + /// + /// Class representing a RGB color + /// + public sealed class RgbColor : Color, IEquatable + { + private const string COLOR_COMPONENT_OUT_OF_RANGE = "Color component value must be between 0.0 (minimum intensity) and 1.0 (maximum intensity)."; + + private readonly double _red = 0; + private readonly double _green = 0; + private readonly double _blue = 0; + + /// + /// Constructor for a + /// + public RgbColor() + { } + + /// + /// Constructor for a + /// + /// The red component of the color + /// The green component of the color + /// The blue component of the color + public RgbColor(double red, double green, double blue) + { + Red = red; + Green = green; + Blue = blue; + } + + /// + /// Construct a from bytes values + /// + /// The red component of the color + /// The green component of the color + /// The blue component of the color + public static RgbColor FromBytes(byte red, byte green, byte blue) + => new RgbColor(red / 255d, green / 255d, blue / 255d); + + /// + /// The red component of this color + /// + /// Value must be between 0.0 (minimum intensity) and 1.0 (maximum intensity). + public double Red + { + get => _red; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Red), COLOR_COMPONENT_OUT_OF_RANGE); + + _red = value; + } + } + + /// + /// The green component of this color + /// + /// Value must be between 0.0 (minimum intensity) and 1.0 (maximum intensity). + public double Green + { + get => _green; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Green), COLOR_COMPONENT_OUT_OF_RANGE); + + _green = value; + } + } + + /// + /// The blue component of this color + /// + /// Value must be between 0.0 (minimum intensity) and 1.0 (maximum intensity). + public double Blue + { + get => _blue; + init + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(Blue), COLOR_COMPONENT_OUT_OF_RANGE); + + _blue = value; + } + } + + /// + public bool Equals(RgbColor? other) + { + return other is not null + && Red == other.Red + && Green == other.Green + && Blue == other.Blue; + } + + /// + public override bool Equals(Color? other) + => other is RgbColor color && Equals(color); + + /// + public override bool Equals(object? obj) + => obj is RgbColor color && Equals(color); + + /// + public override int GetHashCode() + => HashCode.Combine(Red, Green, Blue); + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Dash.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Dash.cs new file mode 100644 index 0000000..1b52347 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/Dash.cs @@ -0,0 +1,18 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics +{ + /// + /// Class representing a dash configuration for a stroking action + /// + public class Dash + { + /// + /// Array representing the dash + /// + public double[] Array { get; set; } = System.Array.Empty(); + + /// + /// The starting phase of the dash + /// + public double Phase { get; set; } = 0; + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/GraphicState.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/GraphicState.cs new file mode 100644 index 0000000..508c077 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/GraphicState.cs @@ -0,0 +1,76 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; +using Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics +{ + /// + /// Class representing the graphic state of a content stream + /// + public sealed class GraphicsState + { + internal GraphicsState(PageResources resources) + { + Resources = resources; + } + + internal PageResources Resources { get; } + + /// + /// The width of the line + /// + public double LineWidth { get; set; } = 1; + + /// + /// The of the fill of the path + /// + /// If null no filling will occur. + public Color? Fill { get; set; } = null; + + /// + /// The of the stroke of the path + /// + /// If null no stroking will occur. + public Color? Stroke { get; set; } = null; + + /// + /// The dash settings of the path + /// + public Dash Dash { get; set; } = new Dash(); + + /// + /// The miter limit + /// + public double MiterLimit { get; set; } = 10; + + /// + /// The of the path + /// + public LineCapStyle LineCap { get; set; } = LineCapStyle.ButtCap; + + /// + /// The of the path + /// + public LineJoinStyle LineJoin { get; set; } = LineJoinStyle.MiterJoin; + + /// + /// The used to determine what areas to fill. + /// + public FillRule FillRule { get; set; } = FillRule.NonZeroWindingNumber; + + internal GraphicsState Clone() + => new GraphicsState(Resources) + { + LineWidth = LineWidth, + Fill = Fill, + Stroke = Stroke, + Dash = new Dash() + { + Array = Dash.Array, + Phase = Dash.Phase + }, + MiterLimit = MiterLimit, + LineCap = LineCap, + LineJoin = LineJoin + }; + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineCapStyle.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineCapStyle.cs new file mode 100644 index 0000000..21a290d --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineCapStyle.cs @@ -0,0 +1,21 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics +{ + /// + /// Enum representing the shape that shall be used at the ends of open subpaths (and dashes, if any) when they are stroked. + /// + public enum LineCapStyle + { + /// + /// The stroke shall be squared off at the endpoint of the path. + /// + ButtCap = 0, + /// + /// A semicircular arc with a diameter equal to the line width shall be drawn around the endpoint and shall be filled in. + /// + RoundCap = 1, + /// + /// The stroke shall continue beyond the endpoint of the path for a distance equal to half the line width and shall be squared off. + /// + ProjectingSquareCap = 2 + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs new file mode 100644 index 0000000..df1ee94 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Graphics/LineJoinStyle.cs @@ -0,0 +1,26 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Graphics +{ + /// + /// Enum representing the line join style. + /// + 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. + /// + MiterJoin = 0, + /// + /// An arc of a circle with a diameter equal to the line width shall be drawn around the point where the two segments meet, + /// connecting the outer edges of the strokes for the two segments. + /// + RoundJoin = 1, + /// + /// The two segments shall be finished with and the resulting notch beyond the ends of the segments shall + /// be filled with a triangle. + /// + BevelJoin = 2 + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/IPdfObject.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/IPdfObject.cs new file mode 100644 index 0000000..986e721 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/IPdfObject.cs @@ -0,0 +1,13 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// Interface representing a pdf object + /// + public interface IPdfObject + { + /// + /// A pdf reference object that can be used to reference to this object + /// + PdfReference Reference { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/Helpers/IdGenerator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/IdGenerator.cs similarity index 87% rename from src/Synercoding.FileFormats.Pdf/Helpers/IdGenerator.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/IdGenerator.cs index 16090e3..c8fb862 100644 --- a/src/Synercoding.FileFormats.Pdf/Helpers/IdGenerator.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/IdGenerator.cs @@ -1,4 +1,4 @@ -namespace Synercoding.FileFormats.Pdf.Helpers +namespace Synercoding.FileFormats.Pdf.LowLevel { internal sealed class IdGenerator { diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/ObjectType.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectType.cs similarity index 67% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/ObjectType.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/ObjectType.cs index bb120c2..79ebdfc 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/ObjectType.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/ObjectType.cs @@ -1,4 +1,4 @@ -namespace Synercoding.FileFormats.Pdf.PdfInternals +namespace Synercoding.FileFormats.Pdf.LowLevel { internal enum ObjectType { diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykNonStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykNonStrokingColorOperator.cs new file mode 100644 index 0000000..5a5ccfc --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykNonStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a non stroking cmyk operation (k) + /// + public struct CmykNonStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public CmykNonStrokingColorOperator(CmykColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public CmykColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykStrokingColorOperator.cs new file mode 100644 index 0000000..5ca4d6c --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/CmykStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a stroking cmyk operation (K) + /// + public struct CmykStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public CmykStrokingColorOperator(CmykColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public CmykColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayNonStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayNonStrokingColorOperator.cs new file mode 100644 index 0000000..ba0812d --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayNonStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a gray non stroking operator (g) + /// + public struct GrayNonStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public GrayNonStrokingColorOperator(GrayColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public GrayColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayStrokingColorOperator.cs new file mode 100644 index 0000000..939de2c --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/GrayStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a gray stroking operator (G) + /// + public struct GrayStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public GrayStrokingColorOperator(GrayColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public GrayColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/NonStrokingColorSpaceOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/NonStrokingColorSpaceOperator.cs new file mode 100644 index 0000000..638419d --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/NonStrokingColorSpaceOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing non stroking color space change operator (cs) + /// + public struct NonStrokingColorSpaceOperator + { + /// + /// Constructor for . + /// + /// The colorspace to use. + public NonStrokingColorSpaceOperator(PdfName colorspace) + { + ColorSpace = colorspace; + } + + /// + /// The of the colorspace that will be set + /// + public PdfName ColorSpace { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbNonStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbNonStrokingColorOperator.cs new file mode 100644 index 0000000..31de12f --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbNonStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a rgb non stroking operator (rg) + /// + public struct RgbNonStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public RgbNonStrokingColorOperator(RgbColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public RgbColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbStrokingColorOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbStrokingColorOperator.cs new file mode 100644 index 0000000..f939ccd --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/RgbStrokingColorOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics.Colors; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing a rgb stroking operator (RG) + /// + public struct RgbStrokingColorOperator + { + /// + /// Constructor for . + /// + /// The color to use. + public RgbStrokingColorOperator(RgbColor color) + { + Color = color; + } + + /// + /// The color used in the operation + /// + public RgbColor Color { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/StrokingColorSpaceOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/StrokingColorSpaceOperator.cs new file mode 100644 index 0000000..8e5aa65 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Color/StrokingColorSpaceOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Color +{ + /// + /// Struct representing stroking color space change operator (CS) + /// + public struct StrokingColorSpaceOperator + { + /// + /// Constructor for . + /// + /// The colorspace to use. + public StrokingColorSpaceOperator(PdfName colorspace) + { + ColorSpace = colorspace; + } + + /// + /// The of the colorspace that will be set + /// + public PdfName ColorSpace { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CloseOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CloseOperator.cs new file mode 100644 index 0000000..08b709d --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CloseOperator.cs @@ -0,0 +1,7 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a close operator (h) + /// + public struct CloseOperator { } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveDualControlPointsOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveDualControlPointsOperator.cs new file mode 100644 index 0000000..1062a64 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveDualControlPointsOperator.cs @@ -0,0 +1,79 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a cubic bézier curve operator (c) + /// + public struct CubicBezierCurveDualControlPointsOperator + { + /// + /// Constructor for + /// + /// The X coordinate of the first control point + /// The Y coordinate of the first control point + /// The X coordinate of the second control point + /// The Y coordinate of the second control point + /// The X coordinate of the end point of the curve + /// The Y coordinate of the end point of the curve + public CubicBezierCurveDualControlPointsOperator(double x1, double y1, double x2, double y2, double x3, double y3) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + X3 = x3; + Y3 = y3; + } + + /// + /// Constructor for + /// + /// The first control point + /// The second control point + /// The end point of the curve + public CubicBezierCurveDualControlPointsOperator(Point cp1, Point cp2, Point endpoint) + { + cp1 = cp1.ConvertTo(Unit.Points); + cp2 = cp2.ConvertTo(Unit.Points); + endpoint = endpoint.ConvertTo(Unit.Points); + + X1 = cp1.X.Raw; + Y1 = cp1.Y.Raw; + X2 = cp2.X.Raw; + Y2 = cp2.Y.Raw; + Y3 = endpoint.Y.Raw; + X3 = endpoint.X.Raw; + } + + /// + /// The X coordinate of the first control point + /// + public double X1 { get; init; } + + /// + /// The Y coordinate of the first control point + /// + public double Y1 { get; init; } + + /// + /// The X coordinate of the second control point + /// + public double X2 { get; init; } + + /// + /// The Y coordinate of the second control point + /// + public double Y2 { get; init; } + + /// + /// The X coordinate of the end point of the curve + /// + public double X3 { get; init; } + + /// + /// The Y coordinate of the end point of the curve + /// + public double Y3 { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveFinalControlPointsOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveFinalControlPointsOperator.cs new file mode 100644 index 0000000..50184db --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveFinalControlPointsOperator.cs @@ -0,0 +1,61 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a cubic bézier curve operator (y) + /// + public struct CubicBezierCurveFinalControlPointsOperator + { + /// + /// Constructor for + /// + /// The X coordinate of the first control point + /// The Y coordinate of the first control point + /// The X coordinate of the end point of the curve + /// The Y coordinate of the end point of the curve + public CubicBezierCurveFinalControlPointsOperator(double x1, double y1, double x3, double y3) + { + X1 = x1; + Y1 = y1; + X3 = x3; + Y3 = y3; + } + + /// + /// Constructor for + /// + /// The first control point + /// The end point of the curve + public CubicBezierCurveFinalControlPointsOperator(Point cp1, Point endpoint) + { + cp1 = cp1.ConvertTo(Unit.Points); + endpoint = endpoint.ConvertTo(Unit.Points); + + X1 = cp1.X.Raw; + Y1 = cp1.Y.Raw; + X3 = endpoint.X.Raw; + Y3 = endpoint.Y.Raw; + } + + /// + /// The X coordinate of the first control point + /// + public double X1 { get; init; } + + /// + /// The Y coordinate of the first control point + /// + public double Y1 { get; init; } + + /// + /// The X coordinate of the end point of the curve + /// + public double X3 { get; init; } + + /// + /// The Y coordinate of the end point of the curve + /// + public double Y3 { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveInitialControlPointsOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveInitialControlPointsOperator.cs new file mode 100644 index 0000000..db0dff0 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/CubicBezierCurveInitialControlPointsOperator.cs @@ -0,0 +1,61 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a cubic bézier curve operator (v) + /// + public struct CubicBezierCurveInitialControlPointsOperator + { + /// + /// Constructor for + /// + /// The X coordinate of the second control point + /// The Y coordinate of the second control point + /// The X coordinate of the end point of the curve + /// The Y coordinate of the end point of the curve + public CubicBezierCurveInitialControlPointsOperator(double x2, double y2, double x3, double y3) + { + X2 = x2; + Y2 = y2; + X3 = x3; + Y3 = y3; + } + + /// + /// Constructor for + /// + /// The second control point + /// The end point of the curve + public CubicBezierCurveInitialControlPointsOperator(Point cp2, Point endpoint) + { + cp2 = cp2.ConvertTo(Unit.Points); + endpoint = endpoint.ConvertTo(Unit.Points); + + X2 = cp2.X.Raw; + Y2 = cp2.Y.Raw; + X3 = endpoint.X.Raw; + Y3 = endpoint.Y.Raw; + } + + /// + /// The X coordinate of the second control point + /// + public double X2 { get; init; } + + /// + /// The Y coordinate of the second control point + /// + public double Y2 { get; init; } + + /// + /// The X coordinate of the end point of the curve + /// + public double X3 { get; init; } + + /// + /// The Y coordinate of the end point of the curve + /// + public double Y3 { get; init; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/LineOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/LineOperator.cs new file mode 100644 index 0000000..692b352 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/LineOperator.cs @@ -0,0 +1,43 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a line operator (l) + /// + public struct LineOperator + { + /// + /// Constructor for a + /// + /// The X coordinate of the line end + /// The Y coordinate of the line end + public LineOperator(double x, double y) + { + X = x; + Y = y; + } + + /// + /// Constructor for a + /// + /// The end point of the line + public LineOperator(Point point) + { + point = point.ConvertTo(Unit.Points); + + X = point.X.Raw; + Y = point.Y.Raw; + } + + /// + /// The X coordinate of the line end + /// + public double X { get; } + + /// + /// The Y coordinate of the line end + /// + public double Y { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/MoveOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/MoveOperator.cs new file mode 100644 index 0000000..92070c6 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/MoveOperator.cs @@ -0,0 +1,43 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a move operator (m) + /// + public struct MoveOperator + { + /// + /// Constructor for a + /// + /// The X coordinate of the move operator + /// The Y coordinate of the move operator + public MoveOperator(double x, double y) + { + X = x; + Y = y; + } + + /// + /// Constructor for a + /// + /// The end point of the line + public MoveOperator(Point point) + { + point = point.ConvertTo(Unit.Points); + + X = point.X.Raw; + Y = point.Y.Raw; + } + + /// + /// The X coordinate of the move operator + /// + public double X { get; } + + /// + /// The Y coordinate of the move operator + /// + public double Y { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/RectangleOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/RectangleOperator.cs new file mode 100644 index 0000000..7c83aca --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Construction/RectangleOperator.cs @@ -0,0 +1,59 @@ +using Synercoding.Primitives; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Construction +{ + /// + /// Struct representing a rectangle operator (re) + /// + public struct RectangleOperator + { + /// + /// Constructor for a + /// + /// The X coordinate of the lower left corner of the rectangle + /// The Y coordinate of the lower left corner of the rectangle + /// The width of the rectangle + /// The height of the rectangle + public RectangleOperator(double x, double y, double width, double height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + /// + /// Constructor for a + /// + /// + public RectangleOperator(Rectangle rectangle) + { + rectangle = rectangle.ConvertTo(Unit.Points); + + X = rectangle.LLX.Raw; + Y = rectangle.LLY.Raw; + Width = rectangle.Width.Raw; + Height = rectangle.Height.Raw; + } + + /// + /// The X coordinate of the lower left corner of the rectangle + /// + public double X { get; } + + /// + /// The Y coordinate of the lower left corner of the rectangle + /// + public double Y { get; } + + /// + /// The width of the rectangle + /// + public double Width { get; } + + /// + /// The height of the rectangle + /// + public double Height { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseAndStrokeOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseAndStrokeOperator.cs new file mode 100644 index 0000000..ea86139 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseAndStrokeOperator.cs @@ -0,0 +1,7 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a close and stroke operator (s) + /// + public struct CloseAndStrokeOperator { } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseFillAndStrokeOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseFillAndStrokeOperator.cs new file mode 100644 index 0000000..12b4e45 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/CloseFillAndStrokeOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a close, fill and stroke operator (b or b*) + /// + public struct CloseFillAndStrokeOperator + { + /// + /// Constructor for + /// + /// The to use to determine what area to fill. + public CloseFillAndStrokeOperator(FillRule fillRule) + { + FillRule = fillRule; + } + + /// + /// The to use during filling. + /// + public FillRule FillRule { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/EndPathOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/EndPathOperator.cs new file mode 100644 index 0000000..b3466f2 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/EndPathOperator.cs @@ -0,0 +1,7 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a end-of-path operator (no fill, no stroke) (n) + /// + public struct EndPathOperator { } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillAndStrokeOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillAndStrokeOperator.cs new file mode 100644 index 0000000..5b857da --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillAndStrokeOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a fill and stroke operator (B or B*) + /// + public struct FillAndStrokeOperator + { + /// + /// Constructor for a + /// + /// The to use to determine what area to fill. + public FillAndStrokeOperator(FillRule fillRule) + { + FillRule = fillRule; + } + + /// + /// The to use during filling. + /// + public FillRule FillRule { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillOperator.cs new file mode 100644 index 0000000..c42fd98 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillOperator.cs @@ -0,0 +1,23 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a fill operator (f or f*) + /// + public struct FillOperator + { + /// + /// Constructor for a + /// + /// The to use to determine what area to fill. + public FillOperator(FillRule fillRule) + { + FillRule = fillRule; + } + + + /// + /// The to use during filling. + /// + public FillRule FillRule { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillRule.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillRule.cs new file mode 100644 index 0000000..58d1cb3 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/FillRule.cs @@ -0,0 +1,17 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Enum representing the ways the area to be filled can be determined + /// + public enum FillRule : byte + { + /// + /// Represents the non-zero winding rule + /// + NonZeroWindingNumber = 0, + /// + /// Represents the even odd rule + /// + EvenOdd = 1 + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/StrokeOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/StrokeOperator.cs new file mode 100644 index 0000000..e71fadf --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/Pathing/Painting/StrokeOperator.cs @@ -0,0 +1,7 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.Pathing.Painting +{ + /// + /// Struct representing a stroke operator (S) + /// + public struct StrokeOperator { } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/DashOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/DashOperator.cs new file mode 100644 index 0000000..b814380 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/DashOperator.cs @@ -0,0 +1,29 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.State +{ + /// + /// Struct representing a dash operator (d) + /// + public struct DashOperator + { + /// + /// Constructor for + /// + /// The dash array + /// The dash phase + public DashOperator(double[] array, double phase) + { + Array = array; + Phase = phase; + } + + /// + /// The dash array + /// + public double[] Array { get; } + + /// + /// The dash phase + /// + public double Phase { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineCapOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineCapOperator.cs new file mode 100644 index 0000000..fb6c399 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineCapOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.State +{ + /// + /// Struct representing a line cap operator (J) + /// + public struct LineCapOperator + { + /// + /// Constructor for + /// + /// The style to use + public LineCapOperator(LineCapStyle style) + { + Style = style; + } + + /// + /// The to use + /// + public LineCapStyle Style { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineJoinOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineJoinOperator.cs new file mode 100644 index 0000000..346207c --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineJoinOperator.cs @@ -0,0 +1,24 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Graphics; + +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.State +{ + /// + /// Struct representing a line join operator (j) + /// + public struct LineJoinOperator + { + /// + /// Constructor for . + /// + /// The line join style to use. + public LineJoinOperator(LineJoinStyle style) + { + Style = style; + } + + /// + /// The to use. + /// + public LineJoinStyle Style { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineWidthOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineWidthOperator.cs new file mode 100644 index 0000000..103d805 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/LineWidthOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.State +{ + /// + /// Struct representing a line width operator (w) + /// + public struct LineWidthOperator + { + /// + /// Constructor for a + /// + /// The width of the line + public LineWidthOperator(double width) + { + Width = width; + } + + /// + /// The width of the line + /// + public double Width { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/MiterLimitOperator.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/MiterLimitOperator.cs new file mode 100644 index 0000000..37f5320 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/Operators/State/MiterLimitOperator.cs @@ -0,0 +1,22 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel.Operators.State +{ + /// + /// Class representing an miter limit operator for a content stream + /// + public struct MiterLimitOperator + { + /// + /// Constructor for + /// + /// The miter limit + public MiterLimitOperator(double limit) + { + Limit = limit; + } + + /// + /// The limit that this operator represents + /// + public double Limit { get; } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs new file mode 100644 index 0000000..b474564 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PageResources.cs @@ -0,0 +1,36 @@ +using Synercoding.FileFormats.Pdf.Internals; +using System; +using System.Collections.Generic; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + internal sealed class PageResources : IDisposable + { + private readonly Map _images = new Map(); + + private int _imageCounter = 0; + + public IReadOnlyDictionary Images + => _images.Forward; + + public void Dispose() + { + foreach (var kv in _images) + kv.Value.Dispose(); + } + + public PdfName AddImageToResources(Image image) + { + if (_images.Reverse.Contains(image)) + return _images.Reverse[image]; + + var key = "Im" + System.Threading.Interlocked.Increment(ref _imageCounter).ToString().PadLeft(6, '0'); + + var pdfName = PdfName.Get(key); + + _images.Add(pdfName, image); + + return pdfName; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs new file mode 100644 index 0000000..f749678 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PageTree.cs @@ -0,0 +1,49 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + internal class PageTree : IPdfObject + { + private readonly IList _pages; + private bool _isWritten; + + public PageTree(PdfReference id) + : this(id, new List()) + { } + + public PageTree(PdfReference id, IList pages) + { + Reference = id; + _pages = pages; + } + + /// + public PdfReference Reference { get; } + + public void AddPage(PdfPage pdfObject) + => _pages.Add(pdfObject); + + internal uint WriteToStream(PdfStream stream) + { + if (_isWritten) + { + throw new InvalidOperationException("Object is already written to stream."); + } + var position = (uint)stream.Position; + + stream.IndirectDictionary(this, static (pageTree, dictionary) => + { + dictionary + .Type(ObjectType.Pages) + .Write(PdfName.Get("Kids"), pageTree._pages.Select(static p => p.Reference).ToArray()) + .Write(PdfName.Get("Count"), pageTree._pages.Count); + }); + _isWritten = true; + + return position; + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs new file mode 100644 index 0000000..008a4b6 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfDictionary.cs @@ -0,0 +1,460 @@ +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using Synercoding.Primitives; +using System; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// Class represents a dictionary in a pdf + /// + public class PdfDictionary + { + private readonly PdfStream _stream; + + /// + /// Constructor for + /// + /// The stream to + public PdfDictionary(PdfStream stream) + { + _stream = stream; + } + + /// + /// Write an array of s 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, PdfReference[] objectReferences) + { + _stream + .Write(key) + .Space() + .Write(objectReferences) + .NewLine(); + + return this; + } + + /// + /// Write a number to the dictionary + /// + /// The key of the item in the dictionary + /// The number to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value) + { + _stream + .Write(key) + .Space() + .Write(value) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value1, double value2) + { + _stream + .Write(key) + .Space() + .Write(value1, value2) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value1, double value2, double value3) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value1, double value2, double value3, double value4) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The fifth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value1, double value2, double value3, double value4, double value5) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4, value5) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The fifth number to write in the array + /// The sixth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, double value1, double value2, double value3, double value4, double value5, double value6) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4, value5, value6) + .NewLine(); + + return this; + } + + /// + /// Write a number to the dictionary + /// + /// The key of the item in the dictionary + /// The number to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, long value) + { + _stream + .Write(key) + .Space() + .Write(value) + .NewLine(); + + return this; + } + + /// + /// Write a number to the dictionary + /// + /// The key of the item in the dictionary + /// The number to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value) + { + _stream + .Write(key) + .Space() + .Write(value) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value1, int value2) + { + _stream + .Write(key) + .Space() + .Write(value1, value2) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value1, int value2, int value3) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int value4) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The fifth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int value4, int value5) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4, value5) + .NewLine(); + + return this; + } + + /// + /// Write an array of numbers to the dictionary + /// + /// The key of the item in the dictionary + /// The first number to write in the array + /// The second number to write in the array + /// The third number to write in the array + /// The fourth number to write in the array + /// The fifth number to write in the array + /// The sixth number to write in the array + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, int value1, int value2, int value3, int value4, int value5, int value6) + { + _stream + .Write(key) + .Space() + .Write(value1, value2, value3, value4, value5, value6) + .NewLine(); + + return this; + } + + /// + /// Write a text to the dictionary + /// + /// The key of the item in the dictionary + /// The text to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, string value) + { + _stream + .Write(key) + .Space() + .Write(value) + .NewLine(); + + return this; + } + + /// + /// Write a to the dictionary + /// + /// The key of the item in the dictionary + /// The to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, PdfName value) + { + _stream + .Write(key) + .Space() + .Write(value) + .NewLine(); + + return this; + } + + + /// + /// Write a to the dictionary + /// + /// The key of the item in the dictionary + /// The to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, PdfReference objectReference) + { + _stream + .Write(key) + .Space() + .Write(objectReference) + .NewLine(); + + return this; + } + + /// + /// Use an action to write things to the stream + /// + /// The type of data to pass to + /// The key of the item in the dictionary + /// Data to use in the + /// The action to use to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, T data, Action rawActions) + { + _stream + .Write(key) + .Space(); + + rawActions(data, _stream); + + _stream.NewLine(); + + return this; + } + + /// + /// Use an action to write things to the stream + /// + /// The key of the item in the dictionary + /// The action to use to write + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, Action rawActions) + => Write(key, rawActions, static (action, stream) => action(stream)); + + /// + /// Write a to the stream + /// + /// The key of the item in the dictionary + /// The to write. + /// The to support chaining operations. + public PdfDictionary Write(PdfName key, Rectangle rectangle) + { + _stream + .Write(key) + .Space() + .Write(rectangle) + .NewLine(); + + return this; + } + + /// + /// Write a rectangle to the stream if it is not null + /// + /// The key of the item in the dictionary + /// The to write. + /// The to support chaining operations. + public PdfDictionary WriteIfNotNull(PdfName key, Rectangle? rectangle) + => rectangle.HasValue + ? Write(key, rectangle.Value) + : this; + + /// + /// Write a number to the stream if it is not null + /// + /// The key of the item in the dictionary + /// The number to write. + /// The to support chaining operations. + public PdfDictionary WriteIfNotNull(PdfName key, int? value) + => value.HasValue + ? Write(key, value.Value) + : this; + + /// + /// Write a number to the stream if it is not null + /// + /// The key of the item in the dictionary + /// The number to write. + /// The to support chaining operations. + public PdfDictionary WriteIfNotNull(PdfName key, double? value) + => value.HasValue + ? Write(key, value.Value) + : this; + + internal PdfDictionary Type(ObjectType objectType) + { + var nameValue = objectType switch + { + ObjectType.Catalog => PdfName.Get("Catalog"), + ObjectType.Page => PdfName.Get("Page"), + ObjectType.Pages => PdfName.Get("Pages"), + ObjectType.XObject => PdfName.Get("XObject"), + _ => throw new NotImplementedException("Unknown objectType: " + objectType) + }; + + _stream + .Write(PdfName.Get("Type")) + .Space() + .Write(nameValue) + .NewLine(); + + return this; + } + + internal PdfDictionary SubType(XObjectSubType subType) + { + var nameValue = subType switch + { + XObjectSubType.Image => PdfName.Get("Image"), + _ => throw new NotImplementedException("Unknown XObjectSubType: " + subType) + }; + + _stream + .Write(PdfName.Get("Subtype")) + .Space() + .Write(nameValue) + .NewLine(); + + return this; + } + } +} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfName.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfName.cs new file mode 100644 index 0000000..43f2c36 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfName.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// A class representing a name object in a pdf. + /// + public class PdfName + { + private static readonly IDictionary _reservedNames = new Dictionary() + { + { "Author", new PdfName("Author") }, + { "BitsPerComponent", new PdfName("BitsPerComponent") }, + { "BleedBox", new PdfName("BleedBox") }, + { "Catalog", new PdfName("Catalog") }, + { "ColorSpace", new PdfName("ColorSpace") }, + { "Contents", new PdfName("Contents") }, + { "Count", new PdfName("Count") }, + { "CreationDate", new PdfName("CreationDate") }, + { "CropBox", new PdfName("CropBox") }, + { "DCTDecode", new PdfName("DCTDecode") }, + { "Decode", new PdfName("Decode") }, + { "DeviceCMYK", new PdfName("DeviceCMYK") }, + { "DeviceGray", new PdfName("DeviceGray") }, + { "DeviceRGB", new PdfName("DeviceRGB") }, + { "Filter", new PdfName("Filter") }, + { "Height", new PdfName("Height") }, + { "Image", new PdfName("Image") }, + { "Info", new PdfName("Info") }, + { "Kids", new PdfName("Kids") }, + { "Length", new PdfName("Length") }, + { "MediaBox", new PdfName("MediaBox") }, + { "Page", new PdfName("Page") }, + { "Pages", new PdfName("Pages") }, + { "Parent", new PdfName("Parent") }, + { "Producer", new PdfName("Producer") }, + { "Resources", new PdfName("Resources") }, + { "Root", new PdfName("Root") }, + { "Rotate", new PdfName("Rotate") }, + { "Size", new PdfName("Size") }, + { "Subtype", new PdfName("Subtype") }, + { "Title", new PdfName("Title") }, + { "TrimBox", new PdfName("TrimBox") }, + { "Type", new PdfName("Type") }, + { "Width", new PdfName("Width") }, + { "XObject", new PdfName("XObject") }, + }; + + /// + /// Get a from a given + /// + /// The name in raw string form + /// A that is based on the given . + public static PdfName Get(string name) + { + if (_reservedNames.TryGetValue(name, out var value)) + return value; + + System.Diagnostics.Debug.WriteLine($"{{ \"{name}\", new PdfName(\"{name}\") }},"); + + return new PdfName(name); + } + + private readonly string _encoded; + + private PdfName(string raw) + { + if (raw.Length > 127) + throw new ArgumentOutOfRangeException(nameof(raw), "The name is too long. Max length of 127 is allowed."); + if (raw.Any(c => c > 0xff)) + throw new ArgumentOutOfRangeException(nameof(raw), "The name contains non-ascii characters, and is thus not allowed."); + _encoded = _encode(raw); + } + + /// + public override string ToString() + => _encoded; + + private static string _encode(string input) + { + int length = input.Length; + + var pdfName = new StringBuilder(length + 20); + pdfName.Append('/'); + var chars = input.ToCharArray(); + + foreach (var c in chars) + { + // 0xff because only ascii is supported in pdf names + var encoded = c switch + { + // Pdf reserved characters + ' ' => "#20", + '#' => "#23", + '%' => "#25", + '(' => "#28", + ')' => "#29", + '/' => "#2f", + '<' => "#3c", + '>' => "#3e", + '[' => "#5b", + ']' => "#5d", + '{' => "#7b", + '}' => "#7d", + // special characters + var cc when cc < 16 => $"#0{Convert.ToString(cc, 16)}", + var cc when cc < 32 => '#' + Convert.ToString(cc, 16), + var cc when cc > 126 => '#' + Convert.ToString(cc, 16), + // "readable characters" + var cc => cc.ToString() + }; + pdfName.Append(encoded); + } + + return pdfName.ToString(); + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/PdfReference.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfReference.cs similarity index 76% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/PdfReference.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/PdfReference.cs index ebc1a98..202092c 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/PdfReference.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfReference.cs @@ -1,4 +1,4 @@ -namespace Synercoding.FileFormats.Pdf.PdfInternals +namespace Synercoding.FileFormats.Pdf.LowLevel { /// /// A struct representing a reference @@ -9,7 +9,7 @@ public readonly struct PdfReference /// Constructor for that uses generation 0 /// /// The id of the reference - public PdfReference(int objectId) + internal PdfReference(int objectId) : this(objectId, 0) { } @@ -18,7 +18,7 @@ public PdfReference(int objectId) /// /// The id of the reference /// The generation of the reference - public PdfReference(int objectId, int generation) + internal PdfReference(int objectId, int generation) { ObjectId = objectId; Generation = generation; @@ -28,9 +28,16 @@ public PdfReference(int objectId, int generation) /// The object id of this reference /// public int ObjectId { get; } + /// /// The generation of this reference /// public int Generation { get; } + + /// + public override string ToString() + { + return $"{ObjectId} {Generation}"; + } } -} +} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs new file mode 100644 index 0000000..361876c --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/PdfStream.cs @@ -0,0 +1,172 @@ +using Synercoding.FileFormats.Pdf.Internals; +using System; +using System.Globalization; +using System.IO; +using System.Text; + +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// Class that represents a wrapper around a stream to make writing PDF instructions easier. + /// + public class PdfStream : IDisposable + { + private const string NUMBER_STRING_FORMAT = "0.0########"; + + /// + /// Constructor for + /// + /// The stream where everything is written to. + public PdfStream(Stream stream) + { + InnerStream = stream; + } + + /// + /// Get the position in the stream + /// + public uint Position + => (uint)InnerStream.Position; + + /// + /// The stream that is wrapped an being written to + /// + protected internal Stream InnerStream { get; } + + /// + /// Write a to the stream. + /// + /// The byte to write. + /// The calling to support chaining operations. + public PdfStream WriteByte(byte b) + { + InnerStream.WriteByte(b); + + return this; + } + + /// + /// Write a to the stream + /// + /// The char to write. + /// The calling to support chaining operations. + public PdfStream Write(char c) + { + return WriteByte((byte)(c & 0xFF)); + } + + /// + /// Write a to the stream + /// + /// The integer to write + /// The calling to support chaining operations. + public PdfStream Write(int value) + { + var intSize = ByteSizes.Size(value); + for (int i = intSize - 1; i >= 0; i--) + { + var result = (byte)('0' + ((int)(value / Math.Pow(10, i)) % 10)); + InnerStream.WriteByte(result); + } + + return this; + } + + /// + /// Write a to the stream + /// + /// The long to write + /// The calling to support chaining operations. + public PdfStream Write(long value) + { + var intSize = ByteSizes.Size(value); + for (int i = intSize - 1; i >= 0; i--) + { + var result = (byte)('0' + ((int)(value / Math.Pow(10, i)) % 10)); + InnerStream.WriteByte(result); + } + + return this; + } + + /// + /// Write a to the stream + /// + /// The double to write + /// The calling to support chaining operations. + public PdfStream Write(double value) + { + var stringValue = value.ToString(NUMBER_STRING_FORMAT, CultureInfo.InvariantCulture); + var bytes = Encoding.ASCII.GetBytes(stringValue); + InnerStream.Write(bytes, 0, bytes.Length); + + return this; + } + + /// + /// Write a to the stream + /// + /// The string to write + /// The calling to support chaining operations. + public PdfStream Write(string text) + { + var bytes = Encoding.ASCII.GetBytes(text); + InnerStream.Write(bytes, 0, bytes.Length); + + return this; + } + + /// + /// Copy bytes to the stream from a buffer + /// + /// The byte buffer to write + /// The offset in the array to start copying + /// The amount of bytes to copy + /// The calling to support chaining operations. + public PdfStream Write(byte[] buffer, int offset, int count) + { + InnerStream.Write(buffer, offset, count); + + return this; + } + + /// + /// Write a of type to the stream + /// + /// The buffer to write + /// The calling to support chaining operations. + public PdfStream Write(ReadOnlySpan buffer) + { + InnerStream.Write(buffer); + + return this; + } + + /// + /// Flush the inner stream + /// + public void Flush() + { + InnerStream.Flush(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Parameter to indicate wheter this method was called from dispose or finalizer + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + InnerStream.Dispose(); + } + } + } +} diff --git a/src/Synercoding.FileFormats.Pdf/LowLevel/StreamFilter.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/StreamFilter.cs new file mode 100644 index 0000000..cf9bd92 --- /dev/null +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/StreamFilter.cs @@ -0,0 +1,14 @@ +namespace Synercoding.FileFormats.Pdf.LowLevel +{ + /// + /// Enum representing different data encoding methods + /// + internal enum StreamFilter + { + /// + /// Decompress data encoded using a DCT (discrete cosine transform) technique based on the JPEG standard, + /// reproducing image sample data that approximates the original data. + /// + DCTDecode + } +} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/XObjectSubType.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/XObjectSubType.cs similarity index 53% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/XObjectSubType.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/XObjectSubType.cs index b5c0d16..1362bb0 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/XObjectSubType.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/XObjectSubType.cs @@ -1,4 +1,4 @@ -namespace Synercoding.FileFormats.Pdf.PdfInternals +namespace Synercoding.FileFormats.Pdf.LowLevel { internal enum XObjectSubType { diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Entry.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Entry.cs similarity index 79% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Entry.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Entry.cs index f43b45a..49a8eb3 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Entry.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Entry.cs @@ -1,9 +1,8 @@ -using Synercoding.FileFormats.Pdf.Helpers; using System; -namespace Synercoding.FileFormats.Pdf.PdfInternals.XRef +namespace Synercoding.FileFormats.Pdf.LowLevel.XRef { - internal struct Entry : ISpanWriteable + internal struct Entry { public Entry(uint data) : this(data, 0, false) @@ -29,8 +28,9 @@ public void FillSpan(Span bytes) _fillSpanLeadingZero(bytes.Slice(11, 5), GenerationNumber); bytes[16] = 0x20; - bytes[17] = IsFree ? (byte)0x66 : (byte)0x6E; - SpanHelper.WriteNewLine(bytes.Slice(18)); + bytes[17] = (byte)( IsFree ? 0x66 : 0x6E ); + bytes[18] = 0x0D; + bytes[19] = 0x0A; } public int ByteSize() @@ -44,7 +44,7 @@ private void _fillSpanLeadingZero(Span span, uint data) for (int i = span.Length - 1; i >= 0; i--) { span[i] = (byte)( '0' + ( val % 10 ) ); - val = val / 10; + val /= 10; } } @@ -54,7 +54,7 @@ private void _fillSpanLeadingZero(Span span, ushort data) for (int i = span.Length - 1; i >= 0; i--) { span[i] = (byte)( '0' + ( val % 10 ) ); - val = val / 10; + val /= 10; } } } diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Section.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Section.cs similarity index 84% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Section.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Section.cs index b86a01a..5cbbcc4 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Section.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Section.cs @@ -1,9 +1,9 @@ -using Synercoding.FileFormats.Pdf.Helpers; +using Synercoding.FileFormats.Pdf.Internals; using System; -namespace Synercoding.FileFormats.Pdf.PdfInternals.XRef +namespace Synercoding.FileFormats.Pdf.LowLevel.XRef { - internal class Section : ISpanWriteable + internal class Section { public Section(int firstObjectNumber, params Entry[] entries) { @@ -26,7 +26,7 @@ public void FillSpan(Span bytes) for (int i = 0; i < Entries.Length; i++) { - var position = bytesFilled + 2 + ( i * 20 ); + var position = bytesFilled + 2 + (i * 20); Entries[i].FillSpan(bytes.Slice(position, 20)); } } @@ -41,4 +41,4 @@ public int ByteSize() return size; } } -} \ No newline at end of file +} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Table.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Table.cs similarity index 67% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Table.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Table.cs index c148a7f..ae42c82 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/Table.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/Table.cs @@ -1,10 +1,8 @@ -using Synercoding.FileFormats.Pdf.Helpers; -using System; -using System.IO; +using System; -namespace Synercoding.FileFormats.Pdf.PdfInternals.XRef +namespace Synercoding.FileFormats.Pdf.LowLevel.XRef { - internal class Table : ISpanWriteable, IStreamWriteable + internal class Table { public Table(params Entry[] entries) { @@ -16,7 +14,18 @@ public Table(params Entry[] entries) public Section Section { get; } - public void FillSpan(Span bytes) + internal uint WriteToStream(PdfStream stream) + { + var position = (uint)stream.Position; + + var bytes = new byte[_byteSize()]; + _fillSpan(bytes); + stream.Write(bytes); + + return position; + } + + private void _fillSpan(Span bytes) { bytes[0] = 0x78; // x bytes[1] = 0x72; // r @@ -29,23 +38,12 @@ public void FillSpan(Span bytes) Section.FillSpan(bytes.Slice(6, freeSize)); } - public int ByteSize() + private int _byteSize() { int size = 6; // xref + CR LF size += Section.ByteSize(); return size; } - - public uint WriteToStream(Stream stream) - { - var position = (uint)stream.Position; - - var bytes = new byte[ByteSize()]; - FillSpan(bytes); - stream.Write(bytes, 0, bytes.Length); - - return position; - } } -} \ No newline at end of file +} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/TableBuilder.cs b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/TableBuilder.cs similarity index 75% rename from src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/TableBuilder.cs rename to src/Synercoding.FileFormats.Pdf/LowLevel/XRef/TableBuilder.cs index 9bbacf5..fb1245d 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/XRef/TableBuilder.cs +++ b/src/Synercoding.FileFormats.Pdf/LowLevel/XRef/TableBuilder.cs @@ -1,8 +1,8 @@ -using Synercoding.FileFormats.Pdf.Helpers; +using System; using System.Collections.Generic; using System.Linq; -namespace Synercoding.FileFormats.Pdf.PdfInternals.XRef +namespace Synercoding.FileFormats.Pdf.LowLevel.XRef { internal class TableBuilder { @@ -27,12 +27,15 @@ public PdfReference GetId(uint position) public void SetPosition(PdfReference id, uint position) { + if (_positions[id] != -1) + throw new InvalidOperationException($"Reference {id} already has a position in the xref table."); + _positions[id] = position; } public bool Validate() { - foreach(var value in _positions.Values) + foreach (var value in _positions.Values) { if (value == -1) { @@ -45,10 +48,10 @@ public bool Validate() public Table GetXRefTable() { var entries = _positions - .OrderBy(x => x.Key.ObjectId) - .Select(x => new Entry((uint)x.Value)) + .OrderBy(static x => x.Key.ObjectId) + .Select(static x => new Entry((uint)x.Value)) .ToArray(); return new Table(entries); } } -} \ No newline at end of file +} diff --git a/src/Synercoding.FileFormats.Pdf/Primitives/Matrix.cs b/src/Synercoding.FileFormats.Pdf/Matrix.cs similarity index 87% rename from src/Synercoding.FileFormats.Pdf/Primitives/Matrix.cs rename to src/Synercoding.FileFormats.Pdf/Matrix.cs index 3d4919f..31f1e8b 100644 --- a/src/Synercoding.FileFormats.Pdf/Primitives/Matrix.cs +++ b/src/Synercoding.FileFormats.Pdf/Matrix.cs @@ -2,7 +2,7 @@ using System; using System.Runtime.CompilerServices; -namespace Synercoding.FileFormats.Pdf.Primitives +namespace Synercoding.FileFormats.Pdf { /// /// Struct that represents a @@ -95,7 +95,7 @@ public Matrix(double a, double b, double c, double d, double e, double f) /// public override bool Equals(object? obj) - => obj is Rectangle other && Equals(other); + => obj is Matrix other && Equals(other); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -199,12 +199,12 @@ public Matrix Translate(Value x, Value y) [MethodImpl(MethodImplOptions.AggressiveInlining)] public Matrix Multiply(in Matrix other) => new Matrix( - a: ( A * other.A ) + ( B * other.C ), - b: ( A * other.B ) + ( B * other.D ), - c: ( C * other.A ) + ( D * other.C ), - d: ( C * other.B ) + ( D * other.D ), - e: ( E * other.A ) + ( F * other.C ) + other.E, - f: ( E * other.B ) + ( F * other.D ) + other.F); + a: (A * other.A) + (B * other.C), + b: (A * other.B) + (B * other.D), + c: (C * other.A) + (D * other.C), + d: (C * other.B) + (D * other.D), + e: (E * other.A) + (F * other.C) + other.E, + f: (E * other.B) + (F * other.D) + other.F); /// /// Create a matrix used for rotation @@ -283,5 +283,23 @@ public static Matrix CreateTranslationMatrix(Value x, Value y) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double _degreeToRad(double degree) => degree * Math.PI / 180; + + /// + /// Indicates whether the left matrix is equal to the right matrix. + /// + /// The on the left side of the == + /// The on the right side of the == + /// true if the left matrix is equal to the right; otherwise, false. + public static bool operator ==(Matrix left, Matrix right) + => left.Equals(right); + + /// + /// Indicates whether the left matrix is not equal to the right matrix. + /// + /// The on the left side of the != + /// The on the right side of the != + /// true if the left matrix is not equal to the right; otherwise, false. + public static bool operator !=(Matrix left, Matrix right) + => !( left == right ); } } diff --git a/src/Synercoding.FileFormats.Pdf/PackageDetails.props b/src/Synercoding.FileFormats.Pdf/PackageDetails.props index 3345bcb..61547f0 100644 --- a/src/Synercoding.FileFormats.Pdf/PackageDetails.props +++ b/src/Synercoding.FileFormats.Pdf/PackageDetails.props @@ -10,7 +10,7 @@ Synercoding.FileFormats.Pdf Synercoding.FileFormats.Pdf Contains classes which makes it easy to quickly create a pdf file. - Improved Matrix and added support for C# 9. + Added support for the drawing of shapes. \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Header.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Header.cs deleted file mode 100644 index 38b75da..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Header.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Synercoding.FileFormats.Pdf.Helpers; -using System; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals -{ - internal struct Header : ISpanWriteable, IStreamWriteable - { - public int ByteSize() - { - return 17; - } - - public void FillSpan(Span bytes) - { - var position = 0; - bytes[position++] = 0x25; // % - bytes[position++] = 0x50; // P - bytes[position++] = 0x44; // D - bytes[position++] = 0x46; // F - bytes[position++] = 0x2D; // - - bytes[position++] = 0x31; // 1 - bytes[position++] = 0x2E; // . - bytes[position++] = 0x37; // 7 - position += SpanHelper.WriteNewLine(bytes.Slice(position)); - bytes[position++] = 0x25; // % - bytes[position++] = 0x81; // binary indicator > 128 - bytes[position++] = 0x82; // binary indicator > 128 - bytes[position++] = 0x83; // binary indicator > 128 - bytes[position++] = 0x84; // binary indicator > 128 - position += SpanHelper.WriteNewLine(bytes.Slice(position)); - } - - public uint WriteToStream(Stream stream) - { - var position = (uint)stream.Position; - - var bytes = new byte[ByteSize()]; - FillSpan(bytes); - stream.Write(bytes, 0, bytes.Length); - - return position; - } - } -} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Catalog.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Catalog.cs deleted file mode 100644 index 54cbd77..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Catalog.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using System; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - internal class Catalog : IPdfObject - { - public Catalog(PdfReference id, PdfReference pageTree) - { - Reference = id; - PageTree = pageTree; - } - - public PdfReference Reference { get; } - - public PdfReference PageTree { get; } - - public bool IsWritten { get; private set; } - - public uint WriteToStream(Stream stream) - { - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - var position = (uint)stream.Position; - - stream.IndirectDictionary(Reference, dictionary => - { - dictionary - .Type(ObjectType.Catalog) - .Write("/Pages", PageTree); - }); - IsWritten = true; - - return position; - } - - public void Dispose() - { } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/ContentStream.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/ContentStream.cs deleted file mode 100644 index 1b8f388..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/ContentStream.cs +++ /dev/null @@ -1,57 +0,0 @@ -using SixLabors.ImageSharp; -using Synercoding.FileFormats.Pdf.Extensions; -using Synercoding.FileFormats.Pdf.Primitives; -using System; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - internal class ContentStream : IPdfObject - { - private readonly Stream _stream = new MemoryStream(); - - public ContentStream(PdfReference id) - { - Reference = id; - } - - public PdfReference Reference { get; } - - public bool IsWritten { get; private set; } - - public ContentStream AddImage(string resourceKey, Matrix matrix) - { - _stream - .Write("q") // Save graphics state - .NewLine() - .Write(matrix) // Apply matrix - .NewLine() - .Write("/" + resourceKey + " Do") // Paint image - .NewLine() - .Write("Q") // Restore graphics state - .NewLine(); - - return this; - } - - public uint WriteToStream(Stream stream) - { - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - var position = (uint)stream.Position; - - _stream.Position = 0; - stream.IndirectStream(Reference, _stream, _ => { }); - IsWritten = true; - - return position; - } - - public void Dispose() - { - _stream.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/DocumentInformationDictionary.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/DocumentInformationDictionary.cs deleted file mode 100644 index e481cb0..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/DocumentInformationDictionary.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using Synercoding.FileFormats.Pdf.Helpers; -using System; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - internal class DocumentInformationDictionary : IPdfObject - { - public DocumentInformationDictionary(PdfReference id) - : this(id, new DocumentInformation()) - { } - - public DocumentInformationDictionary(PdfReference id, DocumentInformation data) - { - Reference = id; - Data = data ?? throw new ArgumentNullException(nameof(data)); - } - - public PdfReference Reference { get; } - - public bool IsWritten { get; private set; } - - public DocumentInformation Data { get; set; } - - public void Dispose() - { } - - public uint WriteToStream(Stream stream) - { - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - var position = (uint)stream.Position; - - stream.IndirectDictionary(Reference, dictionary => - { - if (!string.IsNullOrWhiteSpace(Data.Title)) - dictionary.Write("/Title", PdfTypeHelper.ToPdfHexadecimalString(Data.Title!)); - if (!string.IsNullOrWhiteSpace(Data.Author)) - dictionary.Write("/Author", PdfTypeHelper.ToPdfHexadecimalString(Data.Author!)); - if (!string.IsNullOrWhiteSpace(Data.Subject)) - dictionary.Write("/Subject", PdfTypeHelper.ToPdfHexadecimalString(Data.Subject!)); - if (!string.IsNullOrWhiteSpace(Data.Keywords)) - dictionary.Write("/Keywords", PdfTypeHelper.ToPdfHexadecimalString(Data.Keywords!)); - if (!string.IsNullOrWhiteSpace(Data.Creator)) - dictionary.Write("/Creator", PdfTypeHelper.ToPdfHexadecimalString(Data.Creator!)); - if (!string.IsNullOrWhiteSpace(Data.Producer)) - dictionary.Write("/Producer", PdfTypeHelper.ToPdfHexadecimalString(Data.Producer!)); - if (Data.CreationDate != null) - dictionary.Write("/CreationDate", PdfTypeHelper.ToPdfDate(Data.CreationDate.Value)); - if (Data.ModDate != null) - dictionary.Write("/ModDate", PdfTypeHelper.ToPdfDate(Data.ModDate.Value)); - }); - - IsWritten = true; - - return position; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/IPdfObject.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/IPdfObject.cs deleted file mode 100644 index 340f75f..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/IPdfObject.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Synercoding.FileFormats.Pdf.Helpers; -using System; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - /// - /// Interface representing a pdf object - /// - public interface IPdfObject : IStreamWriteable, IDisposable - { - /// - /// A pdf reference object that can be used to reference to this object - /// - PdfReference Reference { get; } - /// - /// Indicator to check whether this object has been written to the PDF stream - /// - bool IsWritten { get; } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Image.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Image.cs deleted file mode 100644 index 300aca3..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Image.cs +++ /dev/null @@ -1,79 +0,0 @@ -using SixLabors.ImageSharp; -using Synercoding.FileFormats.Pdf.Extensions; -using System; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - /// - /// A class representing an image - /// - public class Image : IPdfObject, IDisposable - { - private readonly SixLabors.ImageSharp.Image _image; - private bool _disposed; - - internal Image(PdfReference id, SixLabors.ImageSharp.Image image) - { - Reference = id; - _image = image; - } - - /// - public PdfReference Reference { get; private set; } - - /// - public bool IsWritten { get; private set; } - - /// - public void Dispose() - { - if(!_disposed) - { - _image.Dispose(); - _disposed = true; - } - } - - /// - public uint WriteToStream(Stream stream) - { - if(_disposed) - { - throw new ObjectDisposedException(nameof(_image), "Internal image is already disposed"); - } - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - IsWritten = true; - - var position = (uint)stream.Position; - - using (var ms = new MemoryStream()) - { - _image.SaveAsJpeg(ms, new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder() - { - Quality = 100, - Subsample = SixLabors.ImageSharp.Formats.Jpeg.JpegSubsample.Ratio420 - }); - ms.Position = 0; - - stream.IndirectStream(Reference, ms, dictionary => - { - dictionary - .Type(ObjectType.XObject) - .SubType(XObjectSubType.Image) - .Write("/Width", _image.Width) - .Write("/Height", _image.Height) - .Write("/ColorSpace", "/DeviceRGB") - .Write("/BitsPerComponent", 8) - .Write("/Decode", "[0.0 1.0 0.0 1.0 0.0 1.0]"); - }, - StreamFilter.DCTDecode); - } - - return position; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Page.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Page.cs deleted file mode 100644 index 1df3759..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/Page.cs +++ /dev/null @@ -1,102 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using Synercoding.FileFormats.Pdf.PdfInternals.XRef; -using Synercoding.Primitives; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - internal class Page : IPdfObject - { - private readonly PdfReference _pageTreeNode; - private readonly PdfPage _page; - private readonly TableBuilder _tableBuilder; - - public Page(TableBuilder tableBuilder, PdfPage page, PdfReference pageTreeNode) - { - _tableBuilder = tableBuilder; - _page = page; - _pageTreeNode = pageTreeNode; - - Reference = tableBuilder.ReserveId(); - } - - public PdfReference Reference { get; private set; } - - public bool IsWritten { get; private set; } - - public void Dispose() - { - _page.ContentStream.Dispose(); - foreach (var image in _page.Images) - { - image.Value.Dispose(); - } - } - - public uint WriteToStream(Stream stream) - { - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - - var position = (uint)stream.Position; - - var dependencies = new List(); - - stream.IndirectDictionary(Reference, dictionary => - { - dictionary - .Type(ObjectType.Page) - .Write("/Parent", _pageTreeNode); - - // Boxes - dictionary - .Write("/MediaBox", _page.MediaBox) - .Write("/CropBox", _page.CropBox) - .Write("/BleedBox", _page.BleedBox) - .Write("/TrimBox", _page.TrimBox); - - // Resources - if (_page.Images.Count == 0) - { - dictionary.Write("/Resources", x => x.EmptyDictionary()); - } - else - { - dictionary.Write("/Resources", x => x.Dictionary(resources => - { - resources.Write("/XObject", y => y.Dictionary(xobject => - { - foreach (var image in _page.Images) - { - xobject.Write("/" + image.Key, image.Value.Reference); - dependencies.Add(image.Value); - } - })); - })); - } - - // Content stream - dictionary.Write("/Contents", _page.ContentStream.Reference); - dependencies.Add(_page.ContentStream); - }); - IsWritten = true; - - _tableBuilder.SetPosition(Reference, position); - - foreach (var dependency in dependencies) - { - if (!dependency.IsWritten) - { - var dependentPosition = dependency.WriteToStream(stream); - _tableBuilder.SetPosition(dependency.Reference, dependentPosition); - } - } - - return position; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/PageTree.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/PageTree.cs deleted file mode 100644 index 938a099..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Objects/PageTree.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Synercoding.FileFormats.Pdf.PdfInternals.Objects -{ - internal class PageTree : IPdfObject - { - private readonly IList _pages; - - public PageTree(PdfReference id, IList pages) - { - Reference = id; - _pages = pages; - } - - public PdfReference Reference { get; private set; } - - public bool IsWritten { get; private set; } - - public uint WriteToStream(Stream stream) - { - if (IsWritten) - { - throw new InvalidOperationException("Object is already written to stream."); - } - var position = (uint)stream.Position; - - stream.IndirectDictionary(Reference, dictionary => - { - dictionary - .Type(ObjectType.Pages) - .Write("/Kids", _pages.Select(p => p.Reference)) - .Write("/Count", _pages.Count()); - }); - IsWritten = true; - - return position; - } - - public void Dispose() - { - - } - } - -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/PdfDictionary.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/PdfDictionary.cs deleted file mode 100644 index d1b76c2..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/PdfDictionary.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using Synercoding.Primitives; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals -{ - internal class PdfDictionary - { - private readonly Stream _stream; - - public PdfDictionary(Stream stream) - { - _stream = stream; - } - - public PdfDictionary SubType(XObjectSubType subType) - { - var nameValue = subType switch - { - XObjectSubType.Image => "/Image", - _ => throw new NotImplementedException("Unknown XObjectSubType: " + subType) - }; - - _stream - .Write("/Subtype") - .Space() - .Write(nameValue) - .NewLine(); - - return this; - } - - public PdfDictionary Type(ObjectType objectType) - { - var nameValue = objectType switch - { - ObjectType.Catalog => "/Catalog", - ObjectType.Page => "/Page", - ObjectType.Pages => "/Pages", - ObjectType.XObject => "/XObject", - _ => throw new NotImplementedException("Unknown objectType: " + objectType) - }; - - _stream - .Write("/Type") - .Space() - .Write(nameValue) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, IEnumerable objectReferences) - { - _stream - .Write(key) - .Space() - .Write(objectReferences) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, int value) - { - _stream - .Write(key) - .Space() - .Write(value) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, long value) - { - _stream - .Write(key) - .Space() - .Write(value) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, string value) - { - _stream - .Write(key) - .Space() - .Write(value) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, PdfReference objectReference) - { - _stream - .Write(key) - .Space() - .Write(objectReference) - .NewLine(); - - return this; - } - - public PdfDictionary Write(string key, Action rawActions) - { - _stream - .Write(key) - .Space(); - rawActions(_stream); - _stream.NewLine(); - - return this; - } - - public PdfDictionary Write(string key, Rectangle rectangle) - { - _stream - .Write(key) - .Space() - .Write(rectangle) - .NewLine(); - - return this; - } - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/StreamFilter.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/StreamFilter.cs deleted file mode 100644 index 5ede2b8..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/StreamFilter.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Synercoding.FileFormats.Pdf.PdfInternals -{ - internal enum StreamFilter - { - DCTDecode - } -} diff --git a/src/Synercoding.FileFormats.Pdf/PdfInternals/Trailer.cs b/src/Synercoding.FileFormats.Pdf/PdfInternals/Trailer.cs deleted file mode 100644 index 98ea5ee..0000000 --- a/src/Synercoding.FileFormats.Pdf/PdfInternals/Trailer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Synercoding.FileFormats.Pdf.Extensions; -using Synercoding.FileFormats.Pdf.Helpers; -using System.IO; - -namespace Synercoding.FileFormats.Pdf.PdfInternals -{ - internal struct Trailer : IStreamWriteable - { - public Trailer(uint startXRef, int size, PdfReference root, PdfReference documentInfo) - { - StartXRef = startXRef; - Size = size; - Root = root; - DocumentInfo = documentInfo; - } - - public uint StartXRef { get; } - public int Size { get; } - public PdfReference Root { get; } - public PdfReference DocumentInfo { get; } - - public uint WriteToStream(Stream stream) - { - var position = (uint)stream.Position; - - var size = Size; - var root = Root; - var info = DocumentInfo; - - stream - .Write("trailer") - .NewLine() - .Dictionary(dictionary => - { - dictionary - .Write("/Size", size) - .Write("/Root", root) - .Write("/Info", info); - }) - .Write("startxref") - .NewLine() - .Write(StartXRef) - .NewLine() - .Write("%%EOF"); - - return position; - } - } -} \ No newline at end of file diff --git a/src/Synercoding.FileFormats.Pdf/PdfPage.cs b/src/Synercoding.FileFormats.Pdf/PdfPage.cs index f2d6031..ee15eca 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfPage.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfPage.cs @@ -1,204 +1,201 @@ -using SixLabors.ImageSharp.Processing; -using Synercoding.FileFormats.Pdf.PdfInternals.Objects; -using Synercoding.FileFormats.Pdf.PdfInternals.XRef; -using Synercoding.FileFormats.Pdf.Primitives; +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using Synercoding.FileFormats.Pdf.LowLevel.XRef; using Synercoding.Primitives; using Synercoding.Primitives.Extensions; -using System.Collections.Generic; -using System.IO; -using ImageSharp = SixLabors.ImageSharp; +using System; namespace Synercoding.FileFormats.Pdf { /// - /// A class that represents a pdf page + /// This class represents a page in a pdf /// - public class PdfPage + public sealed class PdfPage : IPdfObject, IDisposable { - private int _imageCounter = 0; - private readonly Dictionary _images = new Dictionary(); private readonly TableBuilder _tableBuilder; + private readonly PageTree _parent; - private Rectangle _trimBox = Rectangle.Zero; - private Rectangle _bleedBox = Rectangle.Zero; - private Rectangle _cropBox = Rectangle.Zero; + private int? _rotation; + private bool _isWritten; - internal PdfPage(TableBuilder tableBuilder) + internal PdfPage(TableBuilder tableBuilder, PageTree parent) { _tableBuilder = tableBuilder; + _parent = parent; + _parent.AddPage(this); + + Reference = tableBuilder.ReserveId(); ContentStream = new ContentStream(tableBuilder.ReserveId()); } - internal IReadOnlyDictionary Images => _images; - - internal ContentStream ContentStream { get; } + internal PageResources Resources { get; } = new PageResources(); /// - /// The media box of the + /// The content stream of this page /// - public Rectangle MediaBox { get; set; } = Sizes.A4.AsRectangle(); + public ContentStream ContentStream { get; } - /// - /// The cropbox of the , defaults to - /// - public Rectangle CropBox - { - get => _cropBox.Equals(Rectangle.Zero) ? MediaBox : _cropBox; - set => _cropBox = value; - } + /// + public PdfReference Reference { get; } /// - /// The bleedbox of the , defaults to + /// The rotation of how the page is displayed, must be in increments of 90 /// - public Rectangle BleedBox + public int? Rotation { - get => _bleedBox.Equals(Rectangle.Zero) ? CropBox : _bleedBox; - set => _bleedBox = value; + get => _rotation; + set + { + if (value is not null && value % 90 != 0) + throw new ArgumentOutOfRangeException(nameof(Rotation), value, "The provided value can only be increments of 90."); + + _rotation = value; + } } /// - /// The trimbox of the , defaults to + /// The media box of the /// - public Rectangle TrimBox - { - get => _trimBox.Equals(Rectangle.Zero) ? CropBox : _trimBox; - set => _trimBox = value; - } + public Rectangle MediaBox { get; set; } = Sizes.A4.AsRectangle(); /// - /// Add image to the + /// The cropbox of the , defaults to /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(ImageSharp.Image image, Rectangle rectangle) - { - return _addImage(image, rectangle, true); - } + public Rectangle? CropBox { get; set; } /// - /// Add image to the + /// The bleedbox of the /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(byte[] image, Rectangle rectangle) - { - return _addImage(ImageSharp.Image.Load(image), rectangle, false); - } + public Rectangle? BleedBox { get; set; } /// - /// Add image to the + /// The trimbox of the /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(Stream image, Rectangle rectangle) - { - return _addImage(ImageSharp.Image.Load(image), rectangle, false); - } + public Rectangle? TrimBox { get; set; } /// - /// Add image to the + /// The artbox of the /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(ImageSharp.Image image, Matrix matrix) - { - return _addImage(image, matrix, true); - } + public Rectangle? Art { get; set; } /// - /// Add image to the + /// Add an image to the resources of this page /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(byte[] image, Matrix matrix) + /// The image to add + /// The that can be used to reference this image in the + public PdfName AddImageToResources(SixLabors.ImageSharp.Image image) { - return _addImage(ImageSharp.Image.Load(image), matrix, false); + var id = _tableBuilder.ReserveId(); + + var pdfImage = new Image(id, image); + + return Resources.AddImageToResources(pdfImage); } /// - /// Add image to the + /// Add an image to the resources of this page /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(Stream image, Matrix matrix) + /// The image to add + /// The width of the image in the + /// The height of the image in the + /// The that can be used to reference this image in the + public PdfName AddImageToResources(System.IO.Stream jpgStream, int width, int height) { - return _addImage(ImageSharp.Image.Load(image), matrix, false); + var id = _tableBuilder.ReserveId(); + + var pdfImage = new Image(id, jpgStream, width, height); + + return Resources.AddImageToResources(pdfImage); } /// - /// Add image to the + /// Add an image to the resources of this page /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(Image image, Rectangle rectangle) + /// The image to add + /// The that can be used to reference this image in the + public PdfName AddImageToResources(System.IO.Stream imageStream) { - rectangle = rectangle.ConvertTo(Unit.Points); - var matrix = new Matrix( - rectangle.URX.Raw - rectangle.LLX.Raw, 0, - 0, rectangle.URY.Raw - rectangle.LLY.Raw, - rectangle.LLX.Raw, rectangle.LLY.Raw); - return AddImage(image, matrix); + using var image = SixLabors.ImageSharp.Image.Load(imageStream); + return AddImageToResources(image); } /// - /// Add image to the + /// Add an image to the resources of this page /// - /// The image to be added - /// The that represents the placement on the page - /// This so calls can be chained. - public PdfPage AddImage(Image image, Matrix matrix) + /// The image to add + /// The that can be used to reference this image in the + public PdfName AddImageToResources(Image image) { - var key = _addImageToResources(image); - ContentStream.AddImage(key, matrix); - - return this; + return Resources.AddImageToResources(image); } - private PdfPage _addImage(ImageSharp.Image image, Rectangle rectangle, bool clone) + /// + public void Dispose() { - rectangle = rectangle.ConvertTo(Unit.Points); - var matrix = new Matrix( - rectangle.URX.Raw - rectangle.LLX.Raw, 0, - 0, rectangle.URY.Raw - rectangle.LLY.Raw, - rectangle.LLX.Raw, rectangle.LLY.Raw); + Resources.Dispose(); - return _addImage(image, matrix, clone); + ContentStream.Dispose(); } - private PdfPage _addImage(ImageSharp.Image image, Matrix matrix, bool clone) + internal uint WriteToStream(PdfStream stream) { - var key = _addImageToResources(image, clone); - ContentStream.AddImage(key, matrix); - - return this; - } - - private string _addImageToResources(ImageSharp.Image image, bool clone) - { - if (clone) + if (_isWritten) { - image = image.Clone(ctx => { }); + throw new InvalidOperationException("Object is already written to stream."); } - var id = _tableBuilder.ReserveId(); - - return _addImageToResources(new Image(id, image)); - } - - private string _addImageToResources(Image image) - { - var key = "Im" + System.Threading.Interlocked.Increment(ref _imageCounter).ToString().PadLeft(6, '0'); + var position = (uint)stream.Position; + _tableBuilder.SetPosition(Reference, position); - _images.Add(key, image); + stream.IndirectDictionary(this, static (page, dictionary) => + { + dictionary + .Type(ObjectType.Page) + .Write(PdfName.Get("Parent"), page._parent.Reference); + + // Boxes + dictionary + .Write(PdfName.Get("MediaBox"), page.MediaBox) + .WriteIfNotNull(PdfName.Get("CropBox"), page.CropBox) + .WriteIfNotNull(PdfName.Get("BleedBox"), page.BleedBox) + .WriteIfNotNull(PdfName.Get("TrimBox"), page.TrimBox) + .WriteIfNotNull(PdfName.Get("Rotate"), page.Rotation); + + // Resources + dictionary.Write(PdfName.Get("Resources"), page.Resources, static (resources, stream) => stream.Dictionary(resources, static (resources, stream) => + { + if (resources.Images.Count != 0) + { + stream.Write(PdfName.Get("XObject"), resources.Images, static (images, stream) => stream.Dictionary(images, static (images, xobject) => + { + foreach (var image in images) + { + xobject.Write(image.Key, image.Value.Reference); + } + })); + } + })); + + // Content stream + dictionary.Write(PdfName.Get("Contents"), page.ContentStream.Reference); + }); + + _isWritten = true; + + foreach (var kv in Resources.Images) + { + if (kv.Value.TryWriteToStream(stream, out uint dependentPosition)) + { + _tableBuilder.SetPosition(kv.Value.Reference, dependentPosition); + } + } + if (!ContentStream.IsWritten) + { + var dependentPosition = ContentStream.WriteToStream(stream); + _tableBuilder.SetPosition(ContentStream.Reference, dependentPosition); + } - return key; + return position; } } -} \ No newline at end of file +} diff --git a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs index 76e3f8e..7c217c2 100644 --- a/src/Synercoding.FileFormats.Pdf/PdfWriter.cs +++ b/src/Synercoding.FileFormats.Pdf/PdfWriter.cs @@ -1,27 +1,23 @@ -using Synercoding.FileFormats.Pdf.PdfInternals; -using Synercoding.FileFormats.Pdf.PdfInternals.Objects; -using Synercoding.FileFormats.Pdf.PdfInternals.XRef; +using Synercoding.FileFormats.Pdf.LowLevel; +using Synercoding.FileFormats.Pdf.LowLevel.Extensions; +using Synercoding.FileFormats.Pdf.LowLevel.XRef; using System; -using System.Collections.Generic; using System.IO; using System.Reflection; namespace Synercoding.FileFormats.Pdf { /// - /// Main class for writing PDF files to streams + /// This class is the start point for creating a pdf file /// - public class PdfWriter : IDisposable + public sealed class PdfWriter : IDisposable { private readonly bool _ownsStream; - private readonly Stream _stream; + private readonly PdfStream _stream; private readonly TableBuilder _tableBuilder = new TableBuilder(); - private readonly PdfReference _documentInfo; - private readonly PdfReference _pageTreeNode; - private readonly PdfReference _catalog; - - private readonly IList _pageReferences = new List(); + private readonly PageTree _pageTree; + private readonly Catalog _catalog; /// /// Constructor for @@ -38,12 +34,17 @@ public PdfWriter(Stream stream) /// If the stream is owned, then when this is disposed, the stream is also disposed. public PdfWriter(Stream stream, bool ownsStream) { - _stream = stream; - new Header().WriteToStream(stream); + _stream = new PdfStream(stream); + _writeHeader(_stream); + + _pageTree = new PageTree(_tableBuilder.ReserveId()); + _catalog = new Catalog(_tableBuilder.ReserveId(), _pageTree); - _pageTreeNode = _tableBuilder.ReserveId(); - _catalog = _tableBuilder.ReserveId(); - _documentInfo = _tableBuilder.ReserveId(); + DocumentInformation = new DocumentInformation(_tableBuilder.ReserveId()) + { + Producer = $"Synercoding.FileFormats.Pdf {typeof(PdfWriter).GetTypeInfo().Assembly.GetName().Version}", + CreationDate = DateTime.Now + }; _ownsStream = ownsStream; } @@ -51,11 +52,19 @@ public PdfWriter(Stream stream, bool ownsStream) /// /// Document information, such as the author and title /// - public DocumentInformation DocumentInformation { get; } = new DocumentInformation() + public DocumentInformation DocumentInformation { get; } + + /// + /// Set meta information for this document + /// + /// Action used to set meta data + /// Returns this to chain calls + public PdfWriter SetDocumentInfo(Action infoAction) { - Producer = $"Synercoding.FileFormats.Pdf {typeof(PdfWriter).GetTypeInfo().Assembly.GetName().Version}", - CreationDate = DateTime.Now - }; + infoAction(DocumentInformation); + + return this; + } /// /// Add a page to the pdf file @@ -63,22 +72,28 @@ public PdfWriter(Stream stream, bool ownsStream) /// Action used to setup the page /// Returns this to chain calls public PdfWriter AddPage(Action pageAction) - { - var page = new PdfPage(_tableBuilder); - pageAction(page); + => AddPage(pageAction, static (action, page) => action(page)); - using (var obj = new Page(_tableBuilder, page, _pageTreeNode)) + /// + /// Add a page to the pdf file + /// + /// Data passed into the action + /// Action used to setup the page + /// Returns this to chain calls + public PdfWriter AddPage(T data, Action pageAction) + { + using (var page = new PdfPage(_tableBuilder, _pageTree)) { - _pageReferences.Add(obj); + pageAction(data, page); - obj.WriteToStream(_stream); + page.WriteToStream(_stream); } return this; } /// - /// Add an to the pdf file and get the reference returned + /// Add an to the pdf file and get the reference returned /// /// The image that needs to be added. /// The image reference that can be used in pages @@ -88,28 +103,15 @@ public Image AddImage(SixLabors.ImageSharp.Image image) var pdfImage = new Image(id, image); - var position = pdfImage.WriteToStream(_stream); + if (!pdfImage.TryWriteToStream(_stream, out uint position)) + throw new InvalidOperationException("Image was just created but could not be written to stream."); _tableBuilder.SetPosition(id, position); return pdfImage; } - /// - /// Set meta information for this document - /// - /// Action used to set meta data - /// Returns this to chain calls - public PdfWriter SetDocumentInfo(Action infoAction) - { - infoAction(DocumentInformation); - - return this; - } - - /// - /// Close the PDF document by writing the pagetree, catalog, xref table and trailer to the - /// + /// public void Dispose() { _writePageTree(); @@ -128,42 +130,97 @@ public void Dispose() } } + private static void _writeHeader(PdfStream stream) + { + stream.WriteByte(0x25); // % + stream.WriteByte(0x50); // P + stream.WriteByte(0x44); // D + stream.WriteByte(0x46); // F + stream.WriteByte(0x2D); // - + stream.WriteByte(0x31); // 1 + stream.WriteByte(0x2E); // . + stream.WriteByte(0x37); // 7 + stream.WriteByte(0x0D); // CR + stream.WriteByte(0x0A); // LF + stream.WriteByte(0x25); // % + stream.WriteByte(0x81); // binary indicator > 128 + stream.WriteByte(0x82); // binary indicator > 128 + stream.WriteByte(0x83); // binary indicator > 128 + stream.WriteByte(0x84); // binary indicator > 128 + stream.WriteByte(0x0D); // CR + stream.WriteByte(0x0A); // LF + } + private void _writeDocumentInformation() { - _tableBuilder.SetPosition(_documentInfo, (uint)_stream.Position); + _tableBuilder.SetPosition(DocumentInformation.Reference, (uint)_stream.Position); - var info = new DocumentInformationDictionary(_documentInfo, DocumentInformation); - info.WriteToStream(_stream); + DocumentInformation.WriteToStream(_stream); } private void _writePageTree() { - _tableBuilder.SetPosition(_pageTreeNode, (uint)_stream.Position); + _tableBuilder.SetPosition(_pageTree.Reference, (uint)_stream.Position); - var tree = new PageTree(_pageTreeNode, _pageReferences); - tree.WriteToStream(_stream); + _pageTree.WriteToStream(_stream); } private void _writeCatalog() { - _tableBuilder.SetPosition(_catalog, (uint)_stream.Position); + _tableBuilder.SetPosition(_catalog.Reference, (uint)_stream.Position); - var catalog = new Catalog(_catalog, _pageTreeNode); - catalog.WriteToStream(_stream); + _catalog.WriteToStream(_stream); } private void _writePdfEnding() { if (!_tableBuilder.Validate()) - { throw new InvalidOperationException("XRef table is invalid."); - } var xRefTable = _tableBuilder.GetXRefTable(); uint xRefPosition = xRefTable.WriteToStream(_stream); - var trailer = new Trailer(xRefPosition, xRefTable.Section.ObjectCount, _catalog, _documentInfo); + var trailer = new Trailer(xRefPosition, xRefTable.Section.ObjectCount, _catalog, DocumentInformation); trailer.WriteToStream(_stream); } + + private readonly struct Trailer + { + 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; + } + } } -} \ No newline at end of file +} diff --git a/src/Synercoding.FileFormats.Pdf/Synercoding.FileFormats.Pdf.csproj b/src/Synercoding.FileFormats.Pdf/Synercoding.FileFormats.Pdf.csproj index df101ef..ef6dd85 100644 --- a/src/Synercoding.FileFormats.Pdf/Synercoding.FileFormats.Pdf.csproj +++ b/src/Synercoding.FileFormats.Pdf/Synercoding.FileFormats.Pdf.csproj @@ -7,7 +7,7 @@ - +