From 1ef2e127a682737afa28da0c9d2093a3cdb13cfd Mon Sep 17 00:00:00 2001 From: Jason Nelson Date: Thu, 18 Apr 2024 11:58:40 -0700 Subject: [PATCH] Improve Code Quality (#818) * Make AdobeFontMetricsLigature a struct * Make AdobeFontMetricsCharacterSize a struct * Eliminate allocation in CompactFontFormatData * Pass TransformationMatrix by reference * Seal Encoding classes * Make SubTableHeaderEntry a readonly struct * Introduce StringSplitter and eliminate various allocations in GlyphListFactory * Eliminate a few substring allocations * Use char overload on StringBuilder * Eliminate virtual calls on stringIndex * Optimize ReadHelper ReadLong and ReadInt methods * Add additional readonly annotations to PdfRectangle * Optimize NameTokenizer * Eliminate allocation in TrueTypeGlyphTableSubsetter * Use empty arrays * Eliminate allocations in OperationWriteHelper.WriteHex * Use simplified DecryptCbc method on .NET 6+ * Fix windows-1252 encoding not working on net6.0 and 8.0 * Update int buffers to exact unsigned max length and eliminate additional byte allocation * Fix typo * Remove unused constant --- src/UglyToad.PdfPig.Core/PdfRectangle.cs | 8 +- src/UglyToad.PdfPig.Core/ReadHelper.cs | 69 +++++++++-------- .../AdobeFontMetricsCharacterSize.cs | 2 +- .../AdobeFontMetricsLigature.cs | 2 +- .../CompactFontFormatData.cs | 25 ++++--- .../CompactFontFormatEncodingReader.cs | 14 ++-- .../CompactFontFormatIndividualFontParser.cs | 18 +++-- .../CompactFontFormatDictionaryReader.cs | 18 ++--- ...ompactFontFormatPrivateDictionaryReader.cs | 4 +- ...mpactFontFormatTopLevelDictionaryReader.cs | 4 +- .../Encodings/DifferenceBasedEncoding.cs | 2 +- .../Encodings/MacExpertEncoding.cs | 4 +- .../Encodings/MacOsRomanEncoding.cs | 4 +- .../Encodings/MacRomanEncoding.cs | 5 +- .../Encodings/StandardEncoding.cs | 2 +- .../Encodings/SymbolEncoding.cs | 2 +- .../Encodings/WinAnsiEncoding.cs | 2 +- .../Encodings/ZapfDingbatsEncoding.cs | 2 +- src/UglyToad.PdfPig.Fonts/GlyphList.cs | 7 +- src/UglyToad.PdfPig.Fonts/GlyphListFactory.cs | 22 ++++-- .../TrueType/Glyphs/Glyph.cs | 2 +- .../TrueType/Parser/CMapTableParser.cs | 2 +- .../Subsetting/TrueTypeGlyphTableSubsetter.cs | 12 +-- .../Type1/Parser/Type1Tokenizer.cs | 6 +- .../Util/StringExtensions.cs | 28 +++++++ .../Util/StringSplitter.cs | 70 ++++++++++++++++++ .../Documents/GHOSTSCRIPT-698363-0.pdf | Bin 0 -> 36745 bytes .../Integration/EncodingsTests.cs | 16 ++++ .../Integration/IntegrationDocumentTests.cs | 9 ++- .../Integration/StreamProcessorTests.cs | 6 +- .../SkiaHelpers/SkiaGlyphStreamProcessor.cs | 12 +-- .../NameTokenizer.cs | 45 +++++++---- .../Content/BasePageFactory.cs | 4 +- .../Encryption/AesEncryptionHelper.cs | 4 + .../Graphics/BaseStreamProcessor.cs | 8 +- .../Graphics/ContentStreamProcessor.cs | 6 +- .../Graphics/InlineImageBuilder.cs | 3 +- .../Operations/OperationWriteHelper.cs | 14 +++- .../PerformantRectangleTransformer.cs | 2 +- src/UglyToad.PdfPig/Util/Hex.cs | 22 ++++-- src/UglyToad.PdfPig/Util/PatternParser.cs | 4 +- src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs | 2 +- 42 files changed, 326 insertions(+), 167 deletions(-) create mode 100644 src/UglyToad.PdfPig.Fonts/Util/StringExtensions.cs create mode 100644 src/UglyToad.PdfPig.Fonts/Util/StringSplitter.cs create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/GHOSTSCRIPT-698363-0.pdf diff --git a/src/UglyToad.PdfPig.Core/PdfRectangle.cs b/src/UglyToad.PdfPig.Core/PdfRectangle.cs index eef71ec5..f5661b50 100644 --- a/src/UglyToad.PdfPig.Core/PdfRectangle.cs +++ b/src/UglyToad.PdfPig.Core/PdfRectangle.cs @@ -16,22 +16,22 @@ /// /// Top left point of the rectangle. /// - public PdfPoint TopLeft { get; } + public readonly PdfPoint TopLeft { get; } /// /// Top right point of the rectangle. /// - public PdfPoint TopRight { get; } + public readonly PdfPoint TopRight { get; } /// /// Bottom right point of the rectangle. /// - public PdfPoint BottomRight { get; } + public readonly PdfPoint BottomRight { get; } /// /// Bottom left point of the rectangle. /// - public PdfPoint BottomLeft { get; } + public readonly PdfPoint BottomLeft { get; } /// /// Centroid point of the rectangle. diff --git a/src/UglyToad.PdfPig.Core/ReadHelper.cs b/src/UglyToad.PdfPig.Core/ReadHelper.cs index 07a07e07..a40a3725 100644 --- a/src/UglyToad.PdfPig.Core/ReadHelper.cs +++ b/src/UglyToad.PdfPig.Core/ReadHelper.cs @@ -1,8 +1,8 @@ namespace UglyToad.PdfPig.Core { using System; + using System.Buffers.Text; using System.Collections.Generic; - using System.Globalization; using System.Text; #if NET8_0_OR_GREATER @@ -41,8 +41,6 @@ '\f' ]; - private static readonly int MaximumNumberStringLength = long.MaxValue.ToString("D").Length; - /// /// Read a string from the input until a newline. /// @@ -134,7 +132,7 @@ /// public static bool IsWhitespace(byte c) { - return c == 0 || c == 32 || c == AsciiLineFeed || c == AsciiCarriageReturn || c == 9 || c == 12; + return c is 0 or 32 or AsciiLineFeed or AsciiCarriageReturn or 9 or 12; } /// @@ -198,25 +196,24 @@ public static long ReadLong(IInputBytes bytes) { SkipSpaces(bytes); - long retval; - StringBuilder longBuffer = ReadStringNumber(bytes); + Span buffer = stackalloc byte[19]; // max formatted uint64 length - try + ReadNumberAsUtf8Bytes(bytes, buffer, out int bytesRead); + + ReadOnlySpan longBytes = buffer.Slice(0, bytesRead); + + if (Utf8Parser.TryParse(longBytes, out long result, out _)) { - retval = long.Parse(longBuffer.ToString(), CultureInfo.InvariantCulture); + return result; } - catch (FormatException e) + else { - var bytesToReverse = OtherEncodings.StringAsLatin1Bytes(longBuffer.ToString()); - bytes.Seek(bytes.CurrentOffset - bytesToReverse.Length); + bytes.Seek(bytes.CurrentOffset - bytesRead); - throw new InvalidOperationException($"Error: Expected a long type at offset {bytes.CurrentOffset}, instead got \'{longBuffer}\'", e); + throw new InvalidOperationException($"Error: Expected a long type at offset {bytes.CurrentOffset}, instead got \'{OtherEncodings.BytesAsLatin1String(longBytes)}\'"); } - - return retval; } - /// /// Whether the given value is a digit or not. @@ -231,28 +228,29 @@ /// public static int ReadInt(IInputBytes bytes) { - if (bytes == null) + if (bytes is null) { throw new ArgumentNullException(nameof(bytes)); } SkipSpaces(bytes); - int result; - var intBuffer = ReadStringNumber(bytes); + Span buffer = stackalloc byte[10]; // max formatted uint32 length - try + ReadNumberAsUtf8Bytes(bytes, buffer, out int bytesRead); + + var intBytes = buffer.Slice(0, bytesRead); + + if (Utf8Parser.TryParse(intBytes, out int result, out _)) { - result = int.Parse(intBuffer.ToString(), CultureInfo.InvariantCulture); + return result; } - catch (Exception e) + else { - bytes.Seek(bytes.CurrentOffset - OtherEncodings.StringAsLatin1Bytes(intBuffer.ToString()).Length); - - throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}", e); + bytes.Seek(bytes.CurrentOffset - bytesRead); + + throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}, instead got \'{OtherEncodings.BytesAsLatin1String(intBytes)}\'"); } - - return result; } /// @@ -304,25 +302,26 @@ #endif } - private static StringBuilder ReadStringNumber(IInputBytes reader) + private static void ReadNumberAsUtf8Bytes(IInputBytes reader, scoped Span buffer, out int bytesRead) { - byte lastByte; - StringBuilder buffer = new StringBuilder(); + int position = 0; + byte lastByte; + while (reader.MoveNext() && (lastByte = reader.CurrentByte) != ' ' && lastByte != AsciiLineFeed && lastByte != AsciiCarriageReturn && - lastByte != 60 && //see sourceforge bug 1714707 + lastByte != 60 && // see sourceforge bug 1714707 lastByte != '[' && // PDFBOX-1845 lastByte != '(' && // PDFBOX-2579 lastByte != 0) { - buffer.Append((char)lastByte); - - if (buffer.Length > MaximumNumberStringLength) + if (position >= buffer.Length) { - throw new InvalidOperationException($"Number \'{buffer}\' is getting too long, stop reading at offset {reader.CurrentOffset}"); + throw new InvalidOperationException($"Number \'{OtherEncodings.BytesAsLatin1String(buffer.Slice(0, position))}\' is getting too long, stop reading at offset {reader.CurrentOffset}"); } + + buffer[position++] = lastByte; } if (!reader.IsAtEnd()) @@ -330,7 +329,7 @@ reader.Seek(reader.CurrentOffset - 1); } - return buffer; + bytesRead = position; } } } diff --git a/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsCharacterSize.cs b/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsCharacterSize.cs index df4c4d7e..c1be1a5b 100644 --- a/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsCharacterSize.cs +++ b/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsCharacterSize.cs @@ -4,7 +4,7 @@ /// The x and y components of the width vector of the font's characters. /// Presence implies that IsFixedPitch is true. /// - public class AdobeFontMetricsCharacterSize + public readonly struct AdobeFontMetricsCharacterSize { /// /// The horizontal width. diff --git a/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsLigature.cs b/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsLigature.cs index 93e52447..b64817ea 100644 --- a/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsLigature.cs +++ b/src/UglyToad.PdfPig.Fonts/AdobeFontMetrics/AdobeFontMetricsLigature.cs @@ -3,7 +3,7 @@ /// /// A ligature in an Adobe Font Metrics individual character. /// - public class AdobeFontMetricsLigature + public readonly struct AdobeFontMetricsLigature { /// /// The character to join with to form a ligature. diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatData.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatData.cs index b28b6248..a4441905 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatData.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatData.cs @@ -1,10 +1,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat { using System; - using System.Collections.Generic; using System.Diagnostics; using System.Text; - using Core; /// /// Provides access to the raw bytes of this Compact Font Format file with utility methods for reading data types from it. @@ -37,14 +35,7 @@ /// public string ReadString(int length, Encoding encoding) { - var bytes = new byte[length]; - - for (var i = 0; i < bytes.Length; i++) - { - bytes[i] = ReadByte(); - } - - return encoding.GetString(bytes); + return encoding.GetString(ReadSpan(length)); } /// @@ -86,6 +77,20 @@ return value; } + internal ReadOnlySpan ReadSpan(int count) + { + if (Position + count >= dataBytes.Length) + { + throw new IndexOutOfRangeException($"Cannot read past end of data. Attempted to read to {Position + count} when the underlying data is {dataBytes.Length} bytes long."); + } + + var result = dataBytes.Span.Slice(Position + 1, count); + + Position += count; + + return result; + } + /// /// Read byte. /// diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatEncodingReader.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatEncodingReader.cs index 351276a1..b3020b70 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatEncodingReader.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatEncodingReader.cs @@ -8,7 +8,7 @@ internal static class CompactFontFormatEncodingReader { - public static Encoding ReadEncoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList stringIndex) + public static Encoding ReadEncoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan stringIndex) { if (data == null) { @@ -32,7 +32,7 @@ } } - private static CompactFontFormatFormat0Encoding ReadFormat0Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList stringIndex, byte format) + private static CompactFontFormatFormat0Encoding ReadFormat0Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan stringIndex, byte format) { var numberOfCodes = data.ReadCard8(); @@ -45,7 +45,7 @@ values.Add((code, sid, str)); } - IReadOnlyList supplements = new List(); + IReadOnlyList supplements = []; if (HasSupplement(format)) { supplements = ReadSupplement(data, stringIndex); @@ -54,7 +54,7 @@ return new CompactFontFormatFormat0Encoding(values, supplements); } - private static CompactFontFormatFormat1Encoding ReadFormat1Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList stringIndex, byte format) + private static CompactFontFormatFormat1Encoding ReadFormat1Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan stringIndex, byte format) { var numberOfRanges = data.ReadCard8(); @@ -85,7 +85,7 @@ } private static IReadOnlyList ReadSupplement(CompactFontFormatData dataInput, - IReadOnlyList stringIndex) + ReadOnlySpan stringIndex) { var numberOfSupplements = dataInput.ReadCard8(); var supplements = new CompactFontFormatBuiltInEncoding.Supplement[numberOfSupplements]; @@ -101,13 +101,13 @@ return supplements; } - private static string ReadString(int index, IReadOnlyList stringIndex) + private static string ReadString(int index, ReadOnlySpan stringIndex) { if (index >= 0 && index <= 390) { return CompactFontFormatStandardStrings.GetName(index); } - if (index - 391 < stringIndex.Count) + if (index - 391 < stringIndex.Length) { return stringIndex[index - 391]; } diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatIndividualFontParser.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatIndividualFontParser.cs index 843c2ef6..bd26eb17 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatIndividualFontParser.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/CompactFontFormatIndividualFontParser.cs @@ -2,7 +2,6 @@ { using System; using System.Collections.Generic; - using System.Linq; using Charsets; using CharStrings; using Core; @@ -23,7 +22,7 @@ this.privateDictionaryReader = privateDictionaryReader; } - public CompactFontFormatFont Parse(CompactFontFormatData data, string name, ReadOnlySpan topDictionaryIndex, IReadOnlyList stringIndex, + public CompactFontFormatFont Parse(CompactFontFormatData data, string name, ReadOnlySpan topDictionaryIndex, ReadOnlySpan stringIndex, CompactFontFormatIndex globalSubroutineIndex) { var individualData = new CompactFontFormatData(topDictionaryIndex.ToArray()); @@ -127,8 +126,10 @@ return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union.Two(charStrings), fontEncoding); } - private static ICompactFontFormatCharset ReadCharset(CompactFontFormatData data, CompactFontFormatTopLevelDictionary topDictionary, - CompactFontFormatIndex charStringIndex, IReadOnlyList stringIndex) + private static ICompactFontFormatCharset ReadCharset(CompactFontFormatData data, + CompactFontFormatTopLevelDictionary topDictionary, + CompactFontFormatIndex charStringIndex, + ReadOnlySpan stringIndex) { data.Seek(topDictionary.CharSetOffset); @@ -180,13 +181,13 @@ } } - private static string ReadString(int index, IReadOnlyList stringIndex) + private static string ReadString(int index, ReadOnlySpan stringIndex) { if (index >= 0 && index <= 390) { return CompactFontFormatStandardStrings.GetName(index); } - if (index - 391 < stringIndex.Count) + if (index - 391 < stringIndex.Length) { return stringIndex[index - 391]; } @@ -213,9 +214,10 @@ } } - private CompactFontFormatCidFont ReadCidFont(CompactFontFormatData data, CompactFontFormatTopLevelDictionary topLevelDictionary, + private CompactFontFormatCidFont ReadCidFont(CompactFontFormatData data, + CompactFontFormatTopLevelDictionary topLevelDictionary, int numberOfGlyphs, - IReadOnlyList stringIndex, + ReadOnlySpan stringIndex, CompactFontFormatPrivateDictionary privateDictionary, ICompactFontFormatCharset charset, CompactFontFormatIndex globalSubroutines, diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatDictionaryReader.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatDictionaryReader.cs index eeb84521..9ee4f012 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatDictionaryReader.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatDictionaryReader.cs @@ -10,9 +10,9 @@ { private readonly List operands = new List(); - public abstract TResult Read(CompactFontFormatData data, IReadOnlyList stringIndex); + public abstract TResult Read(CompactFontFormatData data, ReadOnlySpan stringIndex); - protected TBuilder ReadDictionary(TBuilder builder, CompactFontFormatData data, IReadOnlyList stringIndex) + protected TBuilder ReadDictionary(TBuilder builder, CompactFontFormatData data, ReadOnlySpan stringIndex) { while (data.CanRead()) { @@ -124,7 +124,7 @@ exponentMissing = false; break; case 0xa: - sb.Append("."); + sb.Append('.'); break; case 0xb: if (hasExponent) @@ -132,7 +132,7 @@ // avoid duplicates break; } - sb.Append("E"); + sb.Append('E'); exponentMissing = true; hasExponent = true; break; @@ -149,7 +149,7 @@ case 0xd: break; case 0xe: - sb.Append("-"); + sb.Append('-'); break; case 0xf: done = true; @@ -165,7 +165,7 @@ // the exponent is missing, just append "0" to avoid an exception // not sure if 0 is the correct value, but it seems to fit // see PDFBOX-1522 - sb.Append("0"); + sb.Append('0'); } if (sb.Length == 0) @@ -176,9 +176,9 @@ return hasExponent ? double.Parse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture) : double.Parse(sb.ToString(), CultureInfo.InvariantCulture); } - protected abstract void ApplyOperation(TBuilder builder, List operands, OperandKey operandKey, IReadOnlyList stringIndex); + protected abstract void ApplyOperation(TBuilder builder, List operands, OperandKey operandKey, ReadOnlySpan stringIndex); - protected static string GetString(List operands, IReadOnlyList stringIndex) + protected static string GetString(List operands, ReadOnlySpan stringIndex) { if (operands.Count == 0) { @@ -198,7 +198,7 @@ } var stringIndexIndex = index - 391; - if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Count) + if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Length) { return stringIndex[stringIndexIndex]; } diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatPrivateDictionaryReader.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatPrivateDictionaryReader.cs index b6a3cb04..9ef68150 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatPrivateDictionaryReader.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatPrivateDictionaryReader.cs @@ -5,7 +5,7 @@ internal class CompactFontFormatPrivateDictionaryReader : CompactFontFormatDictionaryReader { - public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, IReadOnlyList stringIndex) + public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, ReadOnlySpan stringIndex) { var builder = new CompactFontFormatPrivateDictionary.Builder(); @@ -14,7 +14,7 @@ return builder.Build(); } - protected override void ApplyOperation(CompactFontFormatPrivateDictionary.Builder dictionary, List operands, OperandKey operandKey, IReadOnlyList stringIndex) + protected override void ApplyOperation(CompactFontFormatPrivateDictionary.Builder dictionary, List operands, OperandKey operandKey, ReadOnlySpan stringIndex) { switch (operandKey.Byte0) { diff --git a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatTopLevelDictionaryReader.cs b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatTopLevelDictionaryReader.cs index a07172b5..41170345 100644 --- a/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatTopLevelDictionaryReader.cs +++ b/src/UglyToad.PdfPig.Fonts/CompactFontFormat/Dictionaries/CompactFontFormatTopLevelDictionaryReader.cs @@ -6,7 +6,7 @@ internal class CompactFontFormatTopLevelDictionaryReader : CompactFontFormatDictionaryReader { - public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, IReadOnlyList stringIndex) + public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, ReadOnlySpan stringIndex) { var dictionary = new CompactFontFormatTopLevelDictionary(); @@ -15,7 +15,7 @@ return dictionary; } - protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List operands, OperandKey key, IReadOnlyList stringIndex) + protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List operands, OperandKey key, ReadOnlySpan stringIndex) { switch (key.Byte0) { diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/DifferenceBasedEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/DifferenceBasedEncoding.cs index 9493ebb7..f6cfdbe0 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/DifferenceBasedEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/DifferenceBasedEncoding.cs @@ -8,7 +8,7 @@ /// /// Created by combining a base encoding with the differences. /// - public class DifferenceBasedEncoding : Encoding + public sealed class DifferenceBasedEncoding : Encoding { /// public override string EncodingName { get; } = "Difference Encoding"; diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/MacExpertEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/MacExpertEncoding.cs index c8ea6125..608c7df0 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/MacExpertEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/MacExpertEncoding.cs @@ -1,8 +1,6 @@ namespace UglyToad.PdfPig.Fonts.Encodings { - using Core; - - internal class MacExpertEncoding : Encoding + internal sealed class MacExpertEncoding : Encoding { /// /// Table of octal character codes and their corresponding names. diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/MacOsRomanEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/MacOsRomanEncoding.cs index 3bbd06ec..afa0e010 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/MacOsRomanEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/MacOsRomanEncoding.cs @@ -1,12 +1,10 @@ namespace UglyToad.PdfPig.Fonts.Encodings { - using Core; - /// /// /// Similar to the with 15 additional entries. /// - public class MacOsRomanEncoding : MacRomanEncoding + public sealed class MacOsRomanEncoding : MacRomanEncoding { private static readonly (int, string)[] EncodingTable = { diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/MacRomanEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/MacRomanEncoding.cs index 68db2ef1..c2d18829 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/MacRomanEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/MacRomanEncoding.cs @@ -1,8 +1,5 @@ namespace UglyToad.PdfPig.Fonts.Encodings -{ - using Core; - using System.Diagnostics; - +{ /// /// The Mac Roman encoding. /// diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/StandardEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/StandardEncoding.cs index 0ad159f7..562dbe82 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/StandardEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/StandardEncoding.cs @@ -3,7 +3,7 @@ /// /// The standard PDF encoding. /// - public class StandardEncoding : Encoding + public sealed class StandardEncoding : Encoding { private static readonly (int, string)[] EncodingTable = { diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/SymbolEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/SymbolEncoding.cs index c2d3fe92..369ab833 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/SymbolEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/SymbolEncoding.cs @@ -3,7 +3,7 @@ /// /// Symbol encoding. /// - public class SymbolEncoding : Encoding + public sealed class SymbolEncoding : Encoding { /// /// EncodingTable for Symbol diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/WinAnsiEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/WinAnsiEncoding.cs index 6c12e877..28f18fc1 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/WinAnsiEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/WinAnsiEncoding.cs @@ -3,7 +3,7 @@ /// /// Windows ANSI encoding. /// - public class WinAnsiEncoding : Encoding + public sealed class WinAnsiEncoding : Encoding { /// /// The encoding table is taken from the Appendix of the specification. diff --git a/src/UglyToad.PdfPig.Fonts/Encodings/ZapfDingbatsEncoding.cs b/src/UglyToad.PdfPig.Fonts/Encodings/ZapfDingbatsEncoding.cs index f5b885f5..29a38116 100644 --- a/src/UglyToad.PdfPig.Fonts/Encodings/ZapfDingbatsEncoding.cs +++ b/src/UglyToad.PdfPig.Fonts/Encodings/ZapfDingbatsEncoding.cs @@ -3,7 +3,7 @@ /// /// Zapf Dingbats encoding. /// - public class ZapfDingbatsEncoding : Encoding + public sealed class ZapfDingbatsEncoding : Encoding { /// /// EncodingTable for ZapfDingbats diff --git a/src/UglyToad.PdfPig.Fonts/GlyphList.cs b/src/UglyToad.PdfPig.Fonts/GlyphList.cs index 2931f111..f17881ad 100644 --- a/src/UglyToad.PdfPig.Fonts/GlyphList.cs +++ b/src/UglyToad.PdfPig.Fonts/GlyphList.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Text; using Encodings; + using UglyToad.PdfPig.Util; /// /// A list which maps PostScript glyph names to unicode values. @@ -117,7 +118,7 @@ var foundUnicode = true; for (int chPos = 3; chPos + 4 <= nameLength; chPos += 4) { - if (!int.TryParse(name.Substring(chPos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var codePoint)) + if (!int.TryParse(name.AsSpanOrSubstring(chPos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var codePoint)) { foundUnicode = false; break; @@ -138,10 +139,10 @@ unicode = uniStr.ToString(); } - else if (name.StartsWith("u") && name.Length == 5) + else if (name.StartsWith("u", StringComparison.Ordinal) && name.Length == 5) { // test for an alternate Unicode name representation uXXXX - var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + var codePoint = int.Parse(name.AsSpanOrSubstring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture); if (codePoint > 0xD7FF && codePoint < 0xE000) { diff --git a/src/UglyToad.PdfPig.Fonts/GlyphListFactory.cs b/src/UglyToad.PdfPig.Fonts/GlyphListFactory.cs index a5e66982..39a6cedf 100644 --- a/src/UglyToad.PdfPig.Fonts/GlyphListFactory.cs +++ b/src/UglyToad.PdfPig.Fonts/GlyphListFactory.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; + using Util; internal class GlyphListFactory { @@ -34,6 +35,8 @@ return ReadInternal(stream); } + private static readonly char[] Semicolon = [';']; + private static GlyphList ReadInternal(Stream stream, int? defaultDictionaryCapacity = 0) { if (stream == null) @@ -41,8 +44,8 @@ throw new ArgumentNullException(nameof(stream)); } - var result = defaultDictionaryCapacity.HasValue ? new Dictionary(defaultDictionaryCapacity.Value) - : new Dictionary(); + var result = defaultDictionaryCapacity.HasValue ? new Dictionary(defaultDictionaryCapacity.Value) : []; + using (var reader = new StreamReader(stream)) { @@ -60,7 +63,7 @@ continue; } - var parts = line.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + var parts = line.Split(Semicolon, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) { @@ -70,13 +73,16 @@ var key = parts[0]; - var values = parts[1].Split(' '); - + var valueReader = new StringSplitter(parts[1].AsSpan(), ' '); var value = string.Empty; - foreach (var s in values) - { - var code = int.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + while (valueReader.TryRead(out var s)) + { +#if NET6_0_OR_GREATER + var code = int.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture); +#else + var code = int.Parse(s.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture); +#endif value += char.ConvertFromUtf32(code); } diff --git a/src/UglyToad.PdfPig.Fonts/TrueType/Glyphs/Glyph.cs b/src/UglyToad.PdfPig.Fonts/TrueType/Glyphs/Glyph.cs index 632bd57f..42f384d2 100644 --- a/src/UglyToad.PdfPig.Fonts/TrueType/Glyphs/Glyph.cs +++ b/src/UglyToad.PdfPig.Fonts/TrueType/Glyphs/Glyph.cs @@ -39,7 +39,7 @@ public static IGlyphDescription Empty(PdfRectangle bounds) { - return new Glyph(true, new byte[0], new ushort[0], new GlyphPoint[0], bounds); + return new Glyph(true, [], [], [], bounds); } public IGlyphDescription DeepClone() diff --git a/src/UglyToad.PdfPig.Fonts/TrueType/Parser/CMapTableParser.cs b/src/UglyToad.PdfPig.Fonts/TrueType/Parser/CMapTableParser.cs index 0d6e64af..de5e7a5b 100644 --- a/src/UglyToad.PdfPig.Fonts/TrueType/Parser/CMapTableParser.cs +++ b/src/UglyToad.PdfPig.Fonts/TrueType/Parser/CMapTableParser.cs @@ -87,7 +87,7 @@ return new CMapTable(tableVersionNumber, header, tables); } - private class SubTableHeaderEntry + private readonly struct SubTableHeaderEntry { public TrueTypeCMapPlatform PlatformId { get; } diff --git a/src/UglyToad.PdfPig.Fonts/TrueType/Subsetting/TrueTypeGlyphTableSubsetter.cs b/src/UglyToad.PdfPig.Fonts/TrueType/Subsetting/TrueTypeGlyphTableSubsetter.cs index d74df2dd..d508543c 100644 --- a/src/UglyToad.PdfPig.Fonts/TrueType/Subsetting/TrueTypeGlyphTableSubsetter.cs +++ b/src/UglyToad.PdfPig.Fonts/TrueType/Subsetting/TrueTypeGlyphTableSubsetter.cs @@ -65,7 +65,7 @@ var compositeIndicesToReplace = new List<(uint offset, ushort newIndex)>(); - using (var stream = new MemoryStream()) + using (var writer = new ArrayPoolBufferWriter()) { for (var i = 0; i < glyphsToCopy.Count; i++) { @@ -102,7 +102,7 @@ } // Record the glyph location. - glyphLocations.Add((uint)stream.Position); + glyphLocations.Add((uint)writer.WrittenCount); var advanceWidth = advanceWidthTable.HorizontalMetrics[glyphsToCopyOriginalIndex[i]]; advanceWidths.Add(advanceWidth); @@ -123,19 +123,19 @@ glyphBytes[offset] = (byte)(newIndex >> 8); glyphBytes[offset + 1] = (byte)newIndex; } - - stream.Write(glyphBytes, 0, glyphBytes.Length); + + writer.Write(glyphBytes); // Each glyph description must start at a 4 byte boundary. var remainder = glyphBytes.Length % 4; var bytesToPad = remainder == 0 ? 0 : 4 - remainder; for (var j = 0; j < bytesToPad; j++) { - stream.WriteByte(0); + writer.Write(0); } } - var output = stream.ToArray(); + var output = writer.WrittenSpan.ToArray(); glyphLocations.Add((uint)output.Length); var offsets = glyphLocations.ToArray(); diff --git a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Tokenizer.cs b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Tokenizer.cs index 452fa46a..333f008e 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Tokenizer.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Tokenizer.cs @@ -169,7 +169,7 @@ switch (c1) { case 'n': - case 'r': stringBuffer.Append("\n"); break; + case 'r': stringBuffer.Append('\n'); break; case 't': stringBuffer.Append('\t'); break; case 'b': stringBuffer.Append('\b'); break; case 'f': stringBuffer.Append('\f'); break; @@ -180,14 +180,14 @@ // octal \ddd if (char.IsDigit(c1)) { - var rawOctal = new string(new[] { c1, GetNext(), GetNext() }); + var rawOctal = new string([c1, GetNext(), GetNext()]); var code = Convert.ToInt32(rawOctal, 8); stringBuffer.Append((char)code); } break; case '\r': case '\n': - stringBuffer.Append("\n"); + stringBuffer.Append('\n'); break; default: stringBuffer.Append(c); diff --git a/src/UglyToad.PdfPig.Fonts/Util/StringExtensions.cs b/src/UglyToad.PdfPig.Fonts/Util/StringExtensions.cs new file mode 100644 index 00000000..38d4ec5d --- /dev/null +++ b/src/UglyToad.PdfPig.Fonts/Util/StringExtensions.cs @@ -0,0 +1,28 @@ +using System; + +namespace UglyToad.PdfPig.Util; + +internal static class StringExtensions +{ +#if NET + public static ReadOnlySpan AsSpanOrSubstring(this string text, int start) + { + return text.AsSpan(start); + } + + public static ReadOnlySpan AsSpanOrSubstring(this string text, int start, int length) + { + return text.AsSpan(start, length); + } +#else + public static string AsSpanOrSubstring(this string text, int start) + { + return text.Substring(start); + } + + public static string AsSpanOrSubstring(this string text, int start, int length) + { + return text.Substring(start, length); + } +#endif +} diff --git a/src/UglyToad.PdfPig.Fonts/Util/StringSplitter.cs b/src/UglyToad.PdfPig.Fonts/Util/StringSplitter.cs new file mode 100644 index 00000000..e86453d1 --- /dev/null +++ b/src/UglyToad.PdfPig.Fonts/Util/StringSplitter.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +namespace UglyToad.PdfPig.Util; + +internal ref struct StringSplitter(ReadOnlySpan text, char separator) +{ + private readonly ReadOnlySpan text = text; + private readonly char separator = separator; + private int position = 0; + + public bool TryRead(out ReadOnlySpan result) + { + if (IsEof) + { + result = default; + + return false; + } + + int separatorIndex = text.Slice(position).IndexOf(separator); + + if (separatorIndex > -1) + { + result = text.Slice(position, separatorIndex); + + position += separatorIndex + 1; + } + else + { + result = text.Slice(position); + + position = text.Length; + } + + return true; + } + + public ReadOnlySpan Read() + { + if (IsEof) + { + ThrowEof(); + } + + int start = position; + + int separatorIndex = text.Slice(position).IndexOf(separator); + + if (separatorIndex > -1) + { + position += separatorIndex + 1; + + return text.Slice(start, separatorIndex); + } + else + { + position = text.Length; + + return text.Slice(start); + } + } + + public readonly bool IsEof => position == text.Length; + + private static void ThrowEof() + { + throw new EndOfStreamException(); + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/GHOSTSCRIPT-698363-0.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/GHOSTSCRIPT-698363-0.pdf new file mode 100644 index 0000000000000000000000000000000000000000..15cea29c1cbb9cede7ed4dd751338f39df8c90ce GIT binary patch literal 36745 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

wsok!bc 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 P=4QNHs;aL3ZoFIo6h&5i literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/EncodingsTests.cs b/src/UglyToad.PdfPig.Tests/Integration/EncodingsTests.cs index deeda366..a8dd79a3 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/EncodingsTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/EncodingsTests.cs @@ -5,6 +5,22 @@ public class EncodingsTests { + [Fact] + public void Windows1252Encoding() + { + string path = IntegrationHelpers.GetDocumentPath("GHOSTSCRIPT-698363-0.pdf"); + using (var document = PdfDocument.Open(path)) + { + var page = document.GetPage(1); + string actual = string.Concat(page.Letters.Select(l => l.Value)); + + // The expected string value is just here to make sure we have the same results across net versions. + // Feel free to correct/update it if chars are not actually correct. + string expected = "ҘҹЧѥЧКጹѝঐܮ̂ҥ҇ҁӃ࿋\u0c0dҀғҊ˺෨ཌආр෨ཌ̂ҘҹЧѥЧКጹѝঐܮ̂ҥ҇ҁӃ࿋\u0c0dҀғҊ˺෨ཌආр෨ཌ̂ݰႺംࢥ༢࣭\u089aѽ̔ҫһҐ̔ݰႺംࢥ༢࣭\u089aѽ̔ҫһҐ̔"; + Assert.Equal(expected, actual); + } + } + [Fact] public void Issue688() { diff --git a/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs b/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs index 842dfa7d..8b628fc6 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs @@ -3,10 +3,11 @@ public class IntegrationDocumentTests { private static readonly Lazy DocumentFolder = new Lazy(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"))); - private static readonly HashSet _documentsToIgnore = new HashSet() - { - "issue_671.pdf" - }; + private static readonly HashSet _documentsToIgnore = + [ + "issue_671.pdf", + "GHOSTSCRIPT-698363-0.pdf" + ]; [Theory] [MemberData(nameof(GetAllDocuments))] diff --git a/src/UglyToad.PdfPig.Tests/Integration/StreamProcessorTests.cs b/src/UglyToad.PdfPig.Tests/Integration/StreamProcessorTests.cs index 16f5a49c..9998eea1 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/StreamProcessorTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/StreamProcessorTests.cs @@ -148,9 +148,9 @@ int code, string unicode, long currentOffset, - TransformationMatrix renderingMatrix, - TransformationMatrix textMatrix, - TransformationMatrix transformationMatrix, + in TransformationMatrix renderingMatrix, + in TransformationMatrix textMatrix, + in TransformationMatrix transformationMatrix, CharacterBoundingBox characterBoundingBox) { _letters.Add(unicode); diff --git a/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/SkiaHelpers/SkiaGlyphStreamProcessor.cs b/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/SkiaHelpers/SkiaGlyphStreamProcessor.cs index 3253cb1f..d0d82dc5 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/SkiaHelpers/SkiaGlyphStreamProcessor.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/VisualVerification/SkiaHelpers/SkiaGlyphStreamProcessor.cs @@ -69,9 +69,9 @@ int code, string unicode, long currentOffset, - TransformationMatrix renderingMatrix, - TransformationMatrix textMatrix, - TransformationMatrix transformationMatrix, + in TransformationMatrix renderingMatrix, + in TransformationMatrix textMatrix, + in TransformationMatrix transformationMatrix, CharacterBoundingBox characterBoundingBox) { if (textRenderingMode == TextRenderingMode.Neither) @@ -98,9 +98,9 @@ IColor strokingColor, IColor nonStrokingColor, TextRenderingMode textRenderingMode, - TransformationMatrix renderingMatrix, - TransformationMatrix textMatrix, - TransformationMatrix transformationMatrix) + in TransformationMatrix renderingMatrix, + in TransformationMatrix textMatrix, + in TransformationMatrix transformationMatrix) { var transformMatrix = renderingMatrix.ToSKMatrix() .PostConcat(textMatrix.ToSKMatrix()) diff --git a/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs index 03cf97b7..a2d6cfbd 100644 --- a/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs @@ -5,9 +5,18 @@ using Core; using Tokens; +#if NET8_0_OR_GREATER + using System.Text.Unicode; +#endif + internal class NameTokenizer : ITokenizer { - private static readonly ListPool ListPool = new ListPool(10); + static NameTokenizer() + { +#if NET6_0_OR_GREATER + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + } public bool ReadsNextByte { get; } = true; @@ -20,11 +29,11 @@ return false; } - var bytes = ListPool.Borrow(); + using var bytes = new ArrayPoolBufferWriter(); bool escapeActive = false; int postEscapeRead = 0; - var escapedChars = new char[2]; + Span escapedChars = stackalloc char[2]; while (inputBytes.MoveNext()) { @@ -43,10 +52,12 @@ if (postEscapeRead == 2) { - var hex = new string(escapedChars); + int high = escapedChars[0] <= '9' ? escapedChars[0] - '0' : char.ToUpper(escapedChars[0]) - 'A' + 10; + int low = escapedChars[1] <= '9' ? escapedChars[1] - '0' : char.ToUpper(escapedChars[1]) - 'A' + 10; - var characterToWrite = (byte)Convert.ToInt32(hex, 16); - bytes.Add(characterToWrite); + byte characterToWrite = (byte)(high * 16 + low); + + bytes.Write(characterToWrite); escapeActive = false; postEscapeRead = 0; @@ -54,11 +65,11 @@ } else { - bytes.Add((byte)'#'); + bytes.Write((byte)'#'); if (postEscapeRead == 1) { - bytes.Add((byte)escapedChars[0]); + bytes.Write((byte)escapedChars[0]); } if (ReadHelper.IsEndOfName(b)) @@ -75,7 +86,7 @@ continue; } - bytes.Add(b); + bytes.Write(b); escapeActive = false; postEscapeRead = 0; } @@ -87,18 +98,22 @@ } else { - bytes.Add(b); + bytes.Write(b); } } - var byteArray = bytes.ToArray(); +#if NET8_0_OR_GREATER + var byteArray = bytes.WrittenSpan; + bool isValidUtf8 = Utf8.IsValid(byteArray); +#else + var byteArray = bytes.WrittenSpan.ToArray(); + bool isValidUtf8 = ReadHelper.IsValidUtf8(byteArray); +#endif - ListPool.Return(bytes); - - var str = ReadHelper.IsValidUtf8(byteArray) + var str = isValidUtf8 ? Encoding.UTF8.GetString(byteArray) : Encoding.GetEncoding("windows-1252").GetString(byteArray); - + token = NameToken.Create(str); return true; diff --git a/src/UglyToad.PdfPig/Content/BasePageFactory.cs b/src/UglyToad.PdfPig/Content/BasePageFactory.cs index be751220..c80cb32a 100644 --- a/src/UglyToad.PdfPig/Content/BasePageFactory.cs +++ b/src/UglyToad.PdfPig/Content/BasePageFactory.cs @@ -176,7 +176,7 @@ CropBox cropBox, UserSpaceUnit userSpaceUnit, PageRotationDegrees rotation, - TransformationMatrix initialMatrix, + in TransformationMatrix initialMatrix, ReadOnlyMemory contentBytes) { IReadOnlyList operations; @@ -315,7 +315,7 @@ /// /// /// - protected static void ApplyTransformNormalise(TransformationMatrix transformationMatrix, ref MediaBox mediaBox, ref CropBox cropBox) + protected static void ApplyTransformNormalise(in TransformationMatrix transformationMatrix, ref MediaBox mediaBox, ref CropBox cropBox) { if (transformationMatrix != TransformationMatrix.Identity) { diff --git a/src/UglyToad.PdfPig/Encryption/AesEncryptionHelper.cs b/src/UglyToad.PdfPig/Encryption/AesEncryptionHelper.cs index a6be20ae..44c7fcb1 100644 --- a/src/UglyToad.PdfPig/Encryption/AesEncryptionHelper.cs +++ b/src/UglyToad.PdfPig/Encryption/AesEncryptionHelper.cs @@ -26,6 +26,9 @@ aes.Key = finalKey; aes.IV = iv; +#if NET8_0_OR_GREATER + return aes.DecryptCbc(data.AsSpan(iv.Length), iv, PaddingMode.PKCS7); +#else var buffer = new byte[256]; using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) @@ -49,6 +52,7 @@ return output.ToArray(); } } +#endif } } } diff --git a/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs index a6a20c4b..45c66218 100644 --- a/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs @@ -120,7 +120,7 @@ CropBox cropBox, UserSpaceUnit userSpaceUnit, PageRotationDegrees rotation, - TransformationMatrix initialMatrix, + in TransformationMatrix initialMatrix, ParsingOptions parsingOptions) { this.PageNumber = pageNumber; @@ -325,9 +325,9 @@ int code, string unicode, long currentOffset, - TransformationMatrix renderingMatrix, - TransformationMatrix textMatrix, - TransformationMatrix transformationMatrix, + in TransformationMatrix renderingMatrix, + in TransformationMatrix textMatrix, + in TransformationMatrix transformationMatrix, CharacterBoundingBox characterBoundingBox); /// diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index 40d50db7..db4b05ac 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -95,9 +95,9 @@ namespace UglyToad.PdfPig.Graphics int code, string unicode, long currentOffset, - TransformationMatrix renderingMatrix, - TransformationMatrix textMatrix, - TransformationMatrix transformationMatrix, + in TransformationMatrix renderingMatrix, + in TransformationMatrix textMatrix, + in TransformationMatrix transformationMatrix, CharacterBoundingBox characterBoundingBox) { var transformedGlyphBounds = PerformantRectangleTransformer diff --git a/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs b/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs index 4d7d3b67..1f89ac52 100644 --- a/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs +++ b/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs @@ -25,7 +25,8 @@ ///

public ReadOnlyMemory Bytes { get; internal set; } - internal InlineImage CreateInlineImage(TransformationMatrix transformationMatrix, + internal InlineImage CreateInlineImage( + in TransformationMatrix transformationMatrix, ILookupFilterProvider filterProvider, IPdfTokenScanner tokenScanner, RenderingIntent defaultRenderingIntent, diff --git a/src/UglyToad.PdfPig/Graphics/Operations/OperationWriteHelper.cs b/src/UglyToad.PdfPig/Graphics/Operations/OperationWriteHelper.cs index 2b8ce762..d2952ff6 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/OperationWriteHelper.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/OperationWriteHelper.cs @@ -1,10 +1,10 @@ namespace UglyToad.PdfPig.Graphics.Operations { - using PdfPig.Core; using System; using System.Globalization; using System.IO; - using UglyToad.PdfPig.Util; + using PdfPig.Core; + using Util; internal static class OperationWriteHelper { @@ -23,9 +23,15 @@ public static void WriteHex(this Stream stream, ReadOnlySpan bytes) { - var text = Hex.GetString(bytes); + Span hex = bytes.Length <= 64 + ? stackalloc byte[bytes.Length * 2] + : new byte[bytes.Length * 2]; - stream.WriteText($"<{text}>"); + Hex.GetUtf8Chars(bytes, hex); + + stream.WriteByte((byte)'<'); + stream.Write(hex); + stream.WriteByte((byte)'>'); } public static void WriteWhiteSpace(this Stream stream) diff --git a/src/UglyToad.PdfPig/Graphics/PerformantRectangleTransformer.cs b/src/UglyToad.PdfPig/Graphics/PerformantRectangleTransformer.cs index 1e2ac4b0..5097640b 100644 --- a/src/UglyToad.PdfPig/Graphics/PerformantRectangleTransformer.cs +++ b/src/UglyToad.PdfPig/Graphics/PerformantRectangleTransformer.cs @@ -10,7 +10,7 @@ /// /// Transform the rectangle using the matrices. /// - public static PdfRectangle Transform(TransformationMatrix first, TransformationMatrix second, TransformationMatrix third, PdfRectangle rectangle) + public static PdfRectangle Transform(in TransformationMatrix first, in TransformationMatrix second, in TransformationMatrix third, PdfRectangle rectangle) { var tl = rectangle.TopLeft; var tr = rectangle.TopRight; diff --git a/src/UglyToad.PdfPig/Util/Hex.cs b/src/UglyToad.PdfPig/Util/Hex.cs index 240d0af9..3a01904e 100644 --- a/src/UglyToad.PdfPig/Util/Hex.cs +++ b/src/UglyToad.PdfPig/Util/Hex.cs @@ -1,7 +1,6 @@ namespace UglyToad.PdfPig.Util { using System; - using System.Text; /** * Utility functions for hex encoding. @@ -17,7 +16,18 @@ * */ private static readonly char[] HexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']; - + + public static void GetUtf8Chars(ReadOnlySpan bytes, Span utf8Chars) + { + int position = 0; + + foreach (var b in bytes) + { + utf8Chars[position++] = (byte)HexChars[GetHighNibble(b)]; + utf8Chars[position++] = (byte)HexChars[GetLowNibble(b)]; + } + } + /// /// Returns a hex string for the given byte array. /// @@ -26,14 +36,16 @@ #if NET6_0_OR_GREATER return Convert.ToHexString(bytes); #else - var stringBuilder = new StringBuilder(bytes.Length * 2); + var chars = new char[bytes.Length * 2]; + int position = 0; foreach (var b in bytes) { - stringBuilder.Append(HexChars[GetHighNibble(b)]).Append(HexChars[GetLowNibble(b)]); + chars[position++] = HexChars[GetHighNibble(b)]; + chars[position++] = HexChars[GetLowNibble(b)]; } - return stringBuilder.ToString(); + return new string(chars); #endif } diff --git a/src/UglyToad.PdfPig/Util/PatternParser.cs b/src/UglyToad.PdfPig/Util/PatternParser.cs index d1aee3c7..0988cd08 100644 --- a/src/UglyToad.PdfPig/Util/PatternParser.cs +++ b/src/UglyToad.PdfPig/Util/PatternParser.cs @@ -67,7 +67,7 @@ } private static PatternColor CreateTilingPattern(StreamToken patternStream, DictionaryToken patternExtGState, - TransformationMatrix matrix, IPdfTokenScanner scanner) + in TransformationMatrix matrix, IPdfTokenScanner scanner) { if (!patternStream.StreamDictionary.TryGet(NameToken.PaintType, scanner, out var paintTypeToken)) { @@ -113,7 +113,7 @@ } private static PatternColor CreateShadingPattern(DictionaryToken patternDictionary, DictionaryToken? patternExtGState, - TransformationMatrix matrix, IPdfTokenScanner scanner, IResourceStore resourceStore, + in TransformationMatrix matrix, IPdfTokenScanner scanner, IResourceStore resourceStore, ILookupFilterProvider filterProvider) { IToken shadingToken = patternDictionary.Data[NameToken.Shading]; diff --git a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs index fbc48132..fa99a38b 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -989,7 +989,7 @@ return this; } - private List DrawLetters(NameToken? name, string text, IWritingFont font, TransformationMatrix fontMatrix, double fontSize, TransformationMatrix textMatrix) + private List DrawLetters(NameToken? name, string text, IWritingFont font, in TransformationMatrix fontMatrix, double fontSize, TransformationMatrix textMatrix) { var horizontalScaling = 1; var rise = 0;