diff --git a/src/UglyToad.PdfPig.Tests/Writer/TokenWriterTests.cs b/src/UglyToad.PdfPig.Tests/Writer/TokenWriterTests.cs index 017a9b80..18bc7d95 100644 --- a/src/UglyToad.PdfPig.Tests/Writer/TokenWriterTests.cs +++ b/src/UglyToad.PdfPig.Tests/Writer/TokenWriterTests.cs @@ -12,10 +12,11 @@ [Fact] public void EscapeSpecialCharacter() { + var writer = new TokenWriter(); using (var memStream = new MemoryStream()) { - TokenWriter.WriteToken(new StringToken("\\"), memStream); - TokenWriter.WriteToken(new StringToken("(Hello)"), memStream); + writer.WriteToken(new StringToken("\\"), memStream); + writer.WriteToken(new StringToken("(Hello)"), memStream); // Read Test memStream.Position = 0; diff --git a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContent.cs b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContent.cs index 17121168..b81c9759 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContent.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContent.cs @@ -10,6 +10,8 @@ /// public class BeginMarkedContent : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContentWithProperties.cs b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContentWithProperties.cs index 73e7cdbe..75063865 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContentWithProperties.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/BeginMarkedContentWithProperties.cs @@ -11,6 +11,8 @@ /// public class BeginMarkedContentWithProperties : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPoint.cs b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPoint.cs index a78d2b91..c8aeb3c5 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPoint.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPoint.cs @@ -10,6 +10,8 @@ /// public class DesignateMarkedContentPoint : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPointWithProperties.cs b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPointWithProperties.cs index 53fe8aeb..0f282fc0 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPointWithProperties.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/MarkedContent/DesignateMarkedContentPointWithProperties.cs @@ -11,6 +11,8 @@ /// public class DesignateMarkedContentPointWithProperties : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorAdvanced.cs b/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorAdvanced.cs index e1be2adb..42652862 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorAdvanced.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorAdvanced.cs @@ -13,6 +13,8 @@ /// public class SetNonStrokeColorAdvanced : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorSpace.cs b/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorSpace.cs index 7715e82d..5c06d741 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorSpace.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/SetNonStrokeColorSpace.cs @@ -11,6 +11,8 @@ /// public class SetNonStrokeColorSpace : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorAdvanced.cs b/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorAdvanced.cs index 852f4f89..825ece3d 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorAdvanced.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorAdvanced.cs @@ -13,6 +13,8 @@ /// public class SetStrokeColorAdvanced : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorSpace.cs b/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorSpace.cs index 8c03e731..4ae622da 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorSpace.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/SetStrokeColorSpace.cs @@ -11,6 +11,8 @@ /// public class SetStrokeColorSpace : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/TextShowing/ShowTextsWithPositioning.cs b/src/UglyToad.PdfPig/Graphics/Operations/TextShowing/ShowTextsWithPositioning.cs index 78e2d4b1..08695238 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/TextShowing/ShowTextsWithPositioning.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/TextShowing/ShowTextsWithPositioning.cs @@ -15,6 +15,8 @@ /// public class ShowTextsWithPositioning : IGraphicsStateOperation { + private static readonly TokenWriter TokenWriter = new TokenWriter(); + /// /// The symbol for this operation in a stream. /// diff --git a/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs b/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs index 6015bf0d..41768266 100644 --- a/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs @@ -14,6 +14,8 @@ private const string DictToken = "dict"; private const string FindResourceToken = "findresource"; + private static readonly TokenWriter TokenWriter = new TokenWriter(); + public static IReadOnlyList ConvertToCMapStream(IReadOnlyDictionary unicodeToCharacterCode) { using (var memoryStream = new MemoryStream()) diff --git a/src/UglyToad.PdfPig/Writer/ITokenWriter.cs b/src/UglyToad.PdfPig/Writer/ITokenWriter.cs new file mode 100644 index 00000000..ea851127 --- /dev/null +++ b/src/UglyToad.PdfPig/Writer/ITokenWriter.cs @@ -0,0 +1,17 @@ +namespace UglyToad.PdfPig.Writer; + +using System.IO; +using Tokens; + +/// +/// Writes any type of to the corresponding PDF document format output. +/// +public interface ITokenWriter +{ + /// + /// Writes the given input token to the output stream with the correct PDF format and encoding including whitespace and line breaks as applicable. + /// + /// The token to write to the stream. + /// The stream to write the token to. + void WriteToken(IToken token, Stream outputStream); +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs index 65d7ec23..364a57ef 100644 --- a/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs +++ b/src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs @@ -18,6 +18,7 @@ protected bool DisposeStream { get; set; } protected bool Initialized { get; set; } protected int CurrentNumber { get; set; } = 1; + protected readonly static TokenWriter TokenWriter = new TokenWriter(); internal PdfStreamWriter(Stream baseStream, bool disposeStream = true) { diff --git a/src/UglyToad.PdfPig/Writer/TokenWriter.cs b/src/UglyToad.PdfPig/Writer/TokenWriter.cs index 88202ad6..50cbb2ef 100644 --- a/src/UglyToad.PdfPig/Writer/TokenWriter.cs +++ b/src/UglyToad.PdfPig/Writer/TokenWriter.cs @@ -14,10 +14,8 @@ /// /// Writes any type of to the corresponding PDF document format output. /// - public class TokenWriter + public class TokenWriter : ITokenWriter { - private static readonly byte Backslash = GetByte("\\"); - private static readonly byte ArrayStart = GetByte("["); private static readonly byte ArrayEnd = GetByte("]"); @@ -46,10 +44,18 @@ private static readonly byte[] StartXref = OtherEncodings.StringAsLatin1Bytes("startxref"); - private static readonly byte[] StreamStart = OtherEncodings.StringAsLatin1Bytes("stream"); - private static readonly byte[] StreamEnd = OtherEncodings.StringAsLatin1Bytes("endstream"); + /// + /// Bytes that indicate start of stream + /// + protected static readonly byte[] StreamStart = OtherEncodings.StringAsLatin1Bytes("stream"); + + /// + /// Bytes that indicate end start of stream + /// + protected static readonly byte[] StreamEnd = OtherEncodings.StringAsLatin1Bytes("endstream"); private static readonly byte StringStart = GetByte("("); + private static readonly byte StringEnd = GetByte(")"); private static readonly byte[] Trailer = OtherEncodings.StringAsLatin1Bytes("trailer"); @@ -79,7 +85,7 @@ /// /// The token to write to the stream. /// The stream to write the token to. - public static void WriteToken(IToken token, Stream outputStream) + public void WriteToken(IToken token, Stream outputStream) { if (token == null) { @@ -136,7 +142,7 @@ /// The object representing the catalog dictionary which is referenced from the trailer dictionary. /// The output stream to write to. /// The object reference for the document information dictionary if present. - internal static void WriteCrossReferenceTable(IReadOnlyDictionary objectOffsets, + internal void WriteCrossReferenceTable(IReadOnlyDictionary objectOffsets, IndirectReference catalogToken, Stream outputStream, IndirectReference? documentInformationReference) @@ -278,7 +284,7 @@ /// Generation of the indirect object. /// Pre-serialized object contents. /// The stream to write the token to. - internal static void WriteObject(long objectNumber, int generation, byte[] data, Stream outputStream) + internal void WriteObject(long objectNumber, int generation, byte[] data, Stream outputStream) { WriteLong(objectNumber, outputStream); WriteWhitespace(outputStream); @@ -297,14 +303,24 @@ WriteLineBreak(outputStream); } - private static void WriteHex(HexToken hex, Stream stream) + /// + /// Write a hex value to the output stream + /// + /// + /// + protected void WriteHex(HexToken hex, Stream stream) { stream.WriteByte(HexStart); stream.WriteText(hex.GetHexString()); stream.WriteByte(HexEnd); } - private static void WriteArray(ArrayToken array, Stream outputStream) + /// + /// Write an array to the output stream, with whitespace at the end. + /// + /// + /// + protected void WriteArray(ArrayToken array, Stream outputStream) { outputStream.WriteByte(ArrayStart); WriteWhitespace(outputStream); @@ -319,14 +335,24 @@ WriteWhitespace(outputStream); } - private static void WriteBoolean(BooleanToken boolean, Stream outputStream) + /// + /// Write a boolean "true" or "false" to the output stream, with whitespace at the end. + /// + /// + /// + protected void WriteBoolean(BooleanToken boolean, Stream outputStream) { var bytes = boolean.Data ? TrueBytes : FalseBytes; outputStream.Write(bytes, 0, bytes.Length); WriteWhitespace(outputStream); } - private static void WriteComment(CommentToken comment, Stream outputStream) + /// + /// Write a "%comment" in the output stream, with a line break at the end. + /// + /// + /// + protected void WriteComment(CommentToken comment, Stream outputStream) { var bytes = OtherEncodings.StringAsLatin1Bytes(comment.Data); outputStream.WriteByte(Comment); @@ -334,7 +360,12 @@ WriteLineBreak(outputStream); } - private static void WriteDictionary(DictionaryToken dictionary, Stream outputStream) + /// + /// Writes dictionary key/value pairs to output stream as Name/Token pairs. + /// + /// + /// + protected void WriteDictionary(DictionaryToken dictionary, Stream outputStream) { outputStream.Write(DictionaryStart, 0, DictionaryStart.Length); @@ -356,7 +387,12 @@ outputStream.Write(DictionaryEnd, 0, DictionaryEnd.Length); } - private static void WriteIndirectReference(IndirectReferenceToken reference, Stream outputStream) + /// + /// Write an indirect reference to the stream, with whitespace at the end. + /// + /// + /// + protected virtual void WriteIndirectReference(IndirectReferenceToken reference, Stream outputStream) { WriteLong(reference.Data.ObjectNumber, outputStream); WriteWhitespace(outputStream); @@ -368,12 +404,17 @@ WriteWhitespace(outputStream); } - private static void WriteName(NameToken name, Stream outputStream) + /// + /// Write a name to the stream, with whitespace at the end. + /// + /// + /// + protected virtual void WriteName(NameToken name, Stream outputStream) { WriteName(name.Data, outputStream); } - private static void WriteName(string name, Stream outputStream) + private void WriteName(string name, Stream outputStream) { /* * Beginning with PDF 1.2, any character except null (character code 0) may be @@ -404,7 +445,12 @@ WriteWhitespace(outputStream); } - private static void WriteNumber(NumericToken number, Stream outputStream) + /// + /// Write a number to the stream, with whitespace at the end. + /// + /// + /// + protected virtual void WriteNumber(NumericToken number, Stream outputStream) { if (!number.HasDecimalPlaces) { @@ -419,7 +465,15 @@ WriteWhitespace(outputStream); } - private static void WriteObject(ObjectToken objectToken, Stream outputStream) + /// + /// Write an object to the stream, with a line break at the end. It writes the following contents: + /// - "[ObjectNumber] [Generation] obj" + /// - Object data + /// - "endobj" + /// + /// + /// + protected virtual void WriteObject(ObjectToken objectToken, Stream outputStream) { WriteLong(objectToken.Number.ObjectNumber, outputStream); WriteWhitespace(outputStream); @@ -438,7 +492,16 @@ WriteLineBreak(outputStream); } - private static void WriteStream(StreamToken streamToken, Stream outputStream) + /// + /// Write a stream token to the output stream, with the following contents: + /// - Dictionary specifying the length of the stream, any applied compression filters and additional information. + /// - Stream start indicator + /// - Bytes in the StreamToken data + /// - Stream end indicator + /// + /// + /// + protected virtual void WriteStream(StreamToken streamToken, Stream outputStream) { WriteDictionary(streamToken.StreamDictionary, outputStream); WriteLineBreak(outputStream); @@ -449,15 +512,22 @@ outputStream.Write(StreamEnd, 0, StreamEnd.Length); } - private static int[] EscapeNeeded = new int[] + private static readonly int[] EscapeNeeded = new int[] { '\r', '\n', '\t', '\b', '\f', '\\' }; - private static int[] Escaped = new int[] + + private static readonly int[] Escaped = new int[] { 'r', 'n', 't', 'b', 'f', '\\' }; - private static void WriteString(StringToken stringToken, Stream outputStream) + + /// + /// Write string to the stream, with whitespace at the end + /// + /// + /// + protected virtual void WriteString(StringToken stringToken, Stream outputStream) { outputStream.WriteByte(StringStart); @@ -515,29 +585,47 @@ WriteWhitespace(outputStream); } - private static void WriteInt(int value, Stream outputStream) + /// + /// Write an integer to the stream + /// + /// + /// + protected virtual void WriteInt(int value, Stream outputStream) { var bytes = OtherEncodings.StringAsLatin1Bytes(value.ToString("G", CultureInfo.InvariantCulture)); outputStream.Write(bytes, 0, bytes.Length); } - private static void WriteLineBreak(Stream outputStream) + /// + /// Write a line break to the output stream + /// + /// + protected virtual void WriteLineBreak(Stream outputStream) { outputStream.WriteNewLine(); } - private static void WriteLong(long value, Stream outputStream) + /// + /// Write a long to the stream + /// + /// + /// + protected virtual void WriteLong(long value, Stream outputStream) { var bytes = OtherEncodings.StringAsLatin1Bytes(value.ToString("G", CultureInfo.InvariantCulture)); outputStream.Write(bytes, 0, bytes.Length); } - private static void WriteWhitespace(Stream outputStream) + /// + /// Write a space to the output stream + /// + /// + protected virtual void WriteWhitespace(Stream outputStream) { outputStream.WriteByte(Whitespace); } - private static void WriteFirstXrefEmptyEntry(Stream outputStream) + private void WriteFirstXrefEmptyEntry(Stream outputStream) { /* * The first entry in the table (object number 0) is always free and has a generation number of 65,535; @@ -591,5 +679,4 @@ } } } -} - +} \ No newline at end of file