From 3594231c6715591d2b5c6866eb83560574f4e212 Mon Sep 17 00:00:00 2001 From: mvantzet Date: Thu, 15 Dec 2022 18:01:10 +0100 Subject: [PATCH] Introduce ITokenWriter / non-static TokenWriter class. This is the first step in making it possible to override methods in the token writer, for example to filter streams when writing using PdfDocumentBuilder. The second step is injecting ITokenWriter into PdfDocumentBuilder. --- .../Writer/TokenWriterTests.cs | 5 +- .../MarkedContent/BeginMarkedContent.cs | 2 + .../BeginMarkedContentWithProperties.cs | 2 + .../DesignateMarkedContentPoint.cs | 2 + ...signateMarkedContentPointWithProperties.cs | 2 + .../Operations/SetNonStrokeColorAdvanced.cs | 2 + .../Operations/SetNonStrokeColorSpace.cs | 2 + .../Operations/SetStrokeColorAdvanced.cs | 2 + .../Operations/SetStrokeColorSpace.cs | 2 + .../TextShowing/ShowTextsWithPositioning.cs | 2 + .../Writer/Fonts/ToUnicodeCMapBuilder.cs | 2 + src/UglyToad.PdfPig/Writer/ITokenWriter.cs | 17 ++ src/UglyToad.PdfPig/Writer/PdfStreamWriter.cs | 1 + src/UglyToad.PdfPig/Writer/TokenWriter.cs | 145 ++++++++++++++---- 14 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 src/UglyToad.PdfPig/Writer/ITokenWriter.cs 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