diff --git a/src/UglyToad.PdfPig.Core/ArrayPoolBufferWriter.cs b/src/UglyToad.PdfPig.Core/ArrayPoolBufferWriter.cs new file mode 100644 index 000000000..1abd11723 --- /dev/null +++ b/src/UglyToad.PdfPig.Core/ArrayPoolBufferWriter.cs @@ -0,0 +1,148 @@ +using System; +using System.Buffers; + +namespace UglyToad.PdfPig.Core; + +/// +/// Pooled Buffer Writer +/// +public sealed class ArrayPoolBufferWriter : IBufferWriter, IDisposable +{ + private const int DefaultBufferSize = 256; + + private T[] buffer; + private int position; + + /// + /// PooledBufferWriter constructor + /// + public ArrayPoolBufferWriter() + { + buffer = ArrayPool.Shared.Rent(DefaultBufferSize); + position = 0; + } + + /// + /// Constructs a PooledBufferWriter + /// + /// The size of the initial buffer + public ArrayPoolBufferWriter(int size) + { + buffer = ArrayPool.Shared.Rent(size); + position = 0; + } + + /// + /// Advanced the current position + /// + /// + public void Advance(int count) + { + position += count; + } + + /// + /// Writes the provided value + /// + public void Write(T value) + { + GetSpan(1)[0] = value; + + position += 1; + } + + /// + /// Writes the provided values + /// + /// + public void Write(ReadOnlySpan values) + { + values.CopyTo(GetSpan(values.Length)); + + position += values.Length; + } + + /// + /// Returns a writeable block of memory that can be written to + /// + public Memory GetMemory(int sizeHint = 0) + { + EnsureCapacity(sizeHint); + + return buffer.AsMemory(position); + } + + /// + /// Returns a span that can be written to + /// + public Span GetSpan(int sizeHint = 0) + { + EnsureCapacity(sizeHint); + + return buffer.AsSpan(position); + } + + /// + /// Returns the number of bytes written to the buffer + /// + public int WrittenCount => position; + + /// + /// Returns the committed data as Memory + /// + public ReadOnlyMemory WrittenMemory => buffer.AsMemory(0, position); + + /// + /// Returns the committed data as a Span + /// + public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, position); + + private void EnsureCapacity(int sizeHint) + { + if (sizeHint is 0) + { + sizeHint = 1; + } + + if (sizeHint > RemainingBytes) + { + var newBuffer = ArrayPool.Shared.Rent(Math.Max(position + sizeHint, 512)); + + if (buffer.Length != 0) + { + Array.Copy(buffer, 0, newBuffer, 0, position); + ArrayPool.Shared.Return(buffer); + } + + buffer = newBuffer; + } + } + + private int RemainingBytes => buffer.Length - position; + + /// + /// Resets the internal state so the instance can be reused before disposal + /// + /// + public void Reset(bool clearArray = false) + { + position = 0; + + if (clearArray) + { + buffer.AsSpan().Clear(); + } + } + + /// + /// Disposes the buffer and returns any rented memory to the pool + /// + public void Dispose() + { + if (buffer.Length != 0) + { + ArrayPool.Shared.Return(buffer); + buffer = []; + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Core/OctalHelpers.cs b/src/UglyToad.PdfPig.Core/OctalHelpers.cs index 55a43afb9..fc5f743c5 100644 --- a/src/UglyToad.PdfPig.Core/OctalHelpers.cs +++ b/src/UglyToad.PdfPig.Core/OctalHelpers.cs @@ -42,7 +42,7 @@ public static short CharacterToShort(this char c) /// /// Read an integer from octal digits. /// - public static int FromOctalDigits(short[] octal) + public static int FromOctalDigits(ReadOnlySpan octal) { int sum = 0; for (int i = octal.Length - 1; i >= 0; i--) diff --git a/src/UglyToad.PdfPig.Core/OtherEncodings.cs b/src/UglyToad.PdfPig.Core/OtherEncodings.cs index 18f63ad25..f4dbe3f67 100644 --- a/src/UglyToad.PdfPig.Core/OtherEncodings.cs +++ b/src/UglyToad.PdfPig.Core/OtherEncodings.cs @@ -1,7 +1,6 @@ namespace UglyToad.PdfPig.Core { - using System.Collections.Generic; - using System.Linq; + using System; using System.Text; /// @@ -30,31 +29,8 @@ public static byte[] StringAsLatin1Bytes(string s) /// /// Convert the bytes to string using the ISO 8859-1 encoding. /// - public static string BytesAsLatin1String(IReadOnlyList bytes) + public static string BytesAsLatin1String(ReadOnlySpan bytes) { - if (bytes == null) - { - return null; - } - - if (bytes is byte[] arr) - { - return BytesAsLatin1String(arr); - } - - return BytesAsLatin1String(bytes.ToArray()); - } - - /// - /// Convert the bytes to string using the ISO 8859-1 encoding. - /// - public static string BytesAsLatin1String(byte[] bytes) - { - if (bytes == null) - { - return null; - } - return Iso88591.GetString(bytes); } } diff --git a/src/UglyToad.PdfPig.Core/PdfDocEncoding.cs b/src/UglyToad.PdfPig.Core/PdfDocEncoding.cs index e58ad0f71..64080f719 100644 --- a/src/UglyToad.PdfPig.Core/PdfDocEncoding.cs +++ b/src/UglyToad.PdfPig.Core/PdfDocEncoding.cs @@ -1,5 +1,6 @@ namespace UglyToad.PdfPig.Core { + using System; using System.Collections.Generic; /// @@ -263,7 +264,7 @@ static PdfDocEncoding() /// Try to convert raw bytes to a PdfDocEncoding encoded string. If unsupported characters are encountered /// meaning we cannot safely round-trip the value to bytes this will instead return false. /// - public static bool TryConvertBytesToString(byte[] bytes, out string result) + public static bool TryConvertBytesToString(ReadOnlySpan bytes, out string result) { result = null; if (bytes.Length == 0) diff --git a/src/UglyToad.PdfPig.Core/Polyfills/EncodingExtensions.cs b/src/UglyToad.PdfPig.Core/Polyfills/EncodingExtensions.cs new file mode 100644 index 000000000..59831580f --- /dev/null +++ b/src/UglyToad.PdfPig.Core/Polyfills/EncodingExtensions.cs @@ -0,0 +1,19 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 + +namespace System.Text; + +internal static class EncodingExtensions +{ + public static string GetString(this Encoding encoding, ReadOnlySpan bytes) + { + if (bytes.IsEmpty) + { + return string.Empty; + } + + // NOTE: this can be made allocation free by introducing unsafe + return encoding.GetString(bytes.ToArray()); + } +} + +#endif \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Core/ReadHelper.cs b/src/UglyToad.PdfPig.Core/ReadHelper.cs index fe205bfd0..07a07e076 100644 --- a/src/UglyToad.PdfPig.Core/ReadHelper.cs +++ b/src/UglyToad.PdfPig.Core/ReadHelper.cs @@ -5,6 +5,10 @@ using System.Globalization; using System.Text; +#if NET8_0_OR_GREATER + using System.Text.Unicode; +#endif + /// /// Helper methods for reading from PDF files. /// @@ -20,8 +24,8 @@ public static class ReadHelper /// public const byte AsciiCarriageReturn = 13; - private static readonly HashSet EndOfNameCharacters = new HashSet - { + private static readonly HashSet EndOfNameCharacters = + [ ' ', AsciiCarriageReturn, AsciiLineFeed, @@ -35,7 +39,7 @@ public static class ReadHelper '(', 0, '\f' - }; + ]; private static readonly int MaximumNumberStringLength = long.MaxValue.ToString("D").Length; @@ -269,7 +273,11 @@ public static bool IsSpace(int c) /// public static bool IsHex(char ch) { +#if NET8_0_OR_GREATER + return char.IsAsciiHexDigit(ch); +#else return char.IsDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +#endif } /// @@ -277,6 +285,9 @@ public static bool IsHex(char ch) /// public static bool IsValidUtf8(byte[] input) { +#if NET8_0_OR_GREATER + return Utf8.IsValid(input); +#else try { var d = Encoding.UTF8.GetDecoder(); @@ -290,6 +301,7 @@ public static bool IsValidUtf8(byte[] input) { return false; } +#endif } private static StringBuilder ReadStringNumber(IInputBytes reader) diff --git a/src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj b/src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj index 3e7a34c2a..41665c7aa 100644 --- a/src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj +++ b/src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net462;net471;net6.0;net8.0 12 @@ -17,7 +17,10 @@ - + + + + diff --git a/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs b/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs index 104aab190..592a4eb50 100644 --- a/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs +++ b/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs @@ -17,12 +17,12 @@ public static class TextEdgesExtractor /// /// Functions used to define left, middle and right edges. /// - private static readonly Tuple>[] edgesFuncs = new Tuple>[] - { + private static readonly Tuple>[] edgesFuncs = + [ Tuple.Create>(EdgeType.Left, x => Math.Round(x.Left, 0)), // use BoundingBox's left coordinate Tuple.Create>(EdgeType.Mid, x => Math.Round(x.Left + x.Width / 2, 0)), // use BoundingBox's mid coordinate Tuple.Create>(EdgeType.Right, x => Math.Round(x.Right, 0)) // use BoundingBox's right coordinate - }; + ]; /// /// Get the text edges. diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharStringParser.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharStringParser.cs index f1b5cde55..f86ea97c8 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharStringParser.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharStringParser.cs @@ -1,13 +1,13 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings { + using System; + using System.Collections.Generic; using Commands; using Commands.Arithmetic; using Commands.Hint; using Commands.PathConstruction; using Commands.StartFinishOutline; using Core; - using System; - using System.Collections.Generic; /// /// Decodes a set of CharStrings to their corresponding Type 1 BuildChar operations. @@ -73,11 +73,11 @@ public static Type1CharStrings Parse(IReadOnlyList> ParseSingle(IReadOnlyList charStringBytes) + private static IReadOnlyList> ParseSingle(ReadOnlySpan charStringBytes) { var interpreted = new List>(); - for (var i = 0; i < charStringBytes.Count; i++) + for (var i = 0; i < charStringBytes.Length; i++) { var b = charStringBytes[i]; @@ -104,7 +104,7 @@ private static IReadOnlyList> ParseSingle(IReadO return interpreted; } - private static int InterpretNumber(byte b, IReadOnlyList bytes, ref int i) + private static int InterpretNumber(byte b, ReadOnlySpan bytes, ref int i) { if (b >= 32 && b <= 246) { @@ -128,7 +128,7 @@ private static int InterpretNumber(byte b, IReadOnlyList bytes, ref int i) return result; } - public static LazyType1Command GetCommand(byte v, IReadOnlyList bytes, ref int i) + public static LazyType1Command GetCommand(byte v, ReadOnlySpan bytes, ref int i) { switch (v) { diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharstringDecryptedBytes.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharstringDecryptedBytes.cs index 7f6d5148d..7c0c8f0cb 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharstringDecryptedBytes.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Type1CharstringDecryptedBytes.cs @@ -1,12 +1,13 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings { using System; - using System.Collections.Generic; using System.Globalization; internal sealed class Type1CharstringDecryptedBytes { - public IReadOnlyList Bytes { get; } + private readonly byte[] bytes; + + public ReadOnlySpan Bytes => bytes; public int Index { get; } @@ -14,17 +15,17 @@ internal sealed class Type1CharstringDecryptedBytes public SourceType Source { get; } - public Type1CharstringDecryptedBytes(IReadOnlyList bytes, int index) + public Type1CharstringDecryptedBytes(byte[] bytes, int index) { - Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); + this.bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); Index = index; Name = GlyphList.NotDefined; Source = SourceType.Subroutine; } - public Type1CharstringDecryptedBytes(string name, IReadOnlyList bytes, int index) + public Type1CharstringDecryptedBytes(string name, byte[] bytes, int index) { - Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); + this.bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); Index = index; Name = name ?? index.ToString(CultureInfo.InvariantCulture); Source = SourceType.Charstring; @@ -38,7 +39,7 @@ public enum SourceType public override string ToString() { - return $"{Name} {Source} {Index} {Bytes.Count} bytes"; + return $"{Name} {Source} {Index} {Bytes.Length} bytes"; } } } diff --git a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs index cf6bb28b0..f2403e2a2 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs @@ -14,7 +14,7 @@ internal class Type1EncryptedPortionParser private const int Password = 5839; private const int CharstringEncryptionKey = 4330; - public (Type1PrivateDictionary, Type1CharStrings) Parse(IReadOnlyList bytes, bool isLenientParsing) + public (Type1PrivateDictionary, Type1CharStrings) Parse(ReadOnlySpan bytes, bool isLenientParsing) { if (!IsBinary(bytes)) { @@ -23,7 +23,7 @@ internal class Type1EncryptedPortionParser var decrypted = Decrypt(bytes, EexecEncryptionKey, EexecRandomBytes); - if (decrypted.Count == 0) + if (decrypted.Length == 0) { var defaultPrivateDictionary = new Type1PrivateDictionary(new Type1PrivateDictionary.Builder()); var defaultCharstrings = new Type1CharStrings(new Dictionary(), @@ -32,7 +32,7 @@ internal class Type1EncryptedPortionParser return (defaultPrivateDictionary, defaultCharstrings); } - var tokenizer = new Type1Tokenizer(new ByteArrayInputBytes(decrypted)); + var tokenizer = new Type1Tokenizer(new ByteArrayInputBytes([.. decrypted])); /* * After 4 random characters follows the /Private dictionary and the /CharString dictionary. @@ -315,9 +315,9 @@ internal class Type1EncryptedPortionParser /// The first byte must not be whitespace. /// One of the first four ciphertext bytes must not be an ASCII hex character. /// - private static bool IsBinary(IReadOnlyList bytes) + private static bool IsBinary(ReadOnlySpan bytes) { - if (bytes.Count < 4) + if (bytes.Length < 4) { return true; } @@ -340,13 +340,14 @@ private static bool IsBinary(IReadOnlyList bytes) return false; } - private static IReadOnlyList ConvertHexToBinary(IReadOnlyList bytes) + private static ReadOnlySpan ConvertHexToBinary(ReadOnlySpan bytes) { - var result = new List(bytes.Count / 2); + var result = new byte[bytes.Length / 2]; + int index = 0; var last = '\0'; var offset = 0; - for (var i = 0; i < bytes.Count; i++) + for (var i = 0; i < bytes.Length; i++) { var c = (char)bytes[i]; if (!ReadHelper.IsHex(c)) @@ -357,7 +358,7 @@ private static IReadOnlyList ConvertHexToBinary(IReadOnlyList bytes) if (offset == 1) { - result.Add(HexToken.Convert(last, c)); + result[index++] = HexToken.ConvertPair(last, c); offset = 0; } else @@ -371,7 +372,7 @@ private static IReadOnlyList ConvertHexToBinary(IReadOnlyList bytes) return result; } - private static IReadOnlyList Decrypt(IReadOnlyList bytes, int key, int randomBytes) + private static ReadOnlySpan Decrypt(ReadOnlySpan bytes, int key, int randomBytes) { /* * We start with three constants R = 55665, c1 = 52845 and c2 = 22719. @@ -388,17 +389,17 @@ private static IReadOnlyList Decrypt(IReadOnlyList bytes, int key, i return bytes; } - if (randomBytes > bytes.Count || bytes.Count == 0) + if (randomBytes > bytes.Length || bytes.Length == 0) { - return new byte[0]; + return []; } const int c1 = 52845; const int c2 = 22719; - var plainBytes = new byte[bytes.Count - randomBytes]; + var plainBytes = new byte[bytes.Length - randomBytes]; - for (var i = 0; i < bytes.Count; i++) + for (var i = 0; i < bytes.Length; i++) { var cipher = bytes[i] & 0xFF; var plain = cipher ^ key >> 8; @@ -681,13 +682,13 @@ private static IReadOnlyList ReadSubroutines(Type throw new InvalidOperationException($"Found an unexpected token instead of subroutine charstring: {charstring}."); } - if (!isLenientParsing && charstringToken.Data.Count != byteLength) + if (!isLenientParsing && charstringToken.Data.Length != byteLength) { throw new InvalidOperationException($"The subroutine charstring {charstringToken} did not have the expected length of {byteLength}."); } - var subroutine = Decrypt(charstringToken.Data, CharstringEncryptionKey, lenIv); - subroutines.Add(new Type1CharstringDecryptedBytes(subroutine, index)); + var subroutine = Decrypt(charstringToken.Data.Span, CharstringEncryptionKey, lenIv); + subroutines.Add(new Type1CharstringDecryptedBytes([.. subroutine], index)); ReadTillPut(tokenizer); } @@ -732,14 +733,14 @@ private static IReadOnlyList ReadCharStrings(Type throw new InvalidOperationException($"Got wrong type of token, expected charstring, instead got: {charstring}."); } - if (!isLenientParsing && charstringToken.Data.Count != charstringLength) + if (!isLenientParsing && charstringToken.Data.Length != charstringLength) { throw new InvalidOperationException($"The charstring {charstringToken} did not have the expected length of {charstringLength}."); } - var data = Decrypt(charstringToken.Data, CharstringEncryptionKey, lenIv); + var data = Decrypt(charstringToken.Data.Span, CharstringEncryptionKey, lenIv); - results.Add(new Type1CharstringDecryptedBytes(name, data, i)); + results.Add(new Type1CharstringDecryptedBytes(name, [.. data], i)); ReadTillDef(tokenizer); } diff --git a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs index a6be5f626..5a0011999 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs @@ -32,7 +32,7 @@ public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2) // Sometimes the entire PFB file including the header bytes can be included which prevents parsing in the normal way. var isEntirePfbFile = inputBytes.Peek() == PfbFileIndicator; - IReadOnlyList eexecPortion = new byte[0]; + ReadOnlySpan eexecPortion = []; if (isEntirePfbFile) { @@ -77,7 +77,7 @@ public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2) try { - var tempEexecPortion = new List(); + using var tempEexecPortion = new ArrayPoolBufferWriter(); var tokenSet = new PreviousTokenSet(); tokenSet.Add(scanner.CurrentToken); while (scanner.MoveNext()) @@ -100,7 +100,7 @@ public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2) { for (int i = 0; i < offset; i++) { - tempEexecPortion.Add((byte)ClearToMark[i]); + tempEexecPortion.Write((byte)ClearToMark[i]); } } @@ -117,7 +117,7 @@ public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2) continue; } - tempEexecPortion.Add(inputBytes.CurrentByte); + tempEexecPortion.Write(inputBytes.CurrentByte); } } else @@ -131,7 +131,7 @@ public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2) if (!isEntirePfbFile) { - eexecPortion = tempEexecPortion; + eexecPortion = tempEexecPortion.WrittenSpan.ToArray(); } } finally diff --git a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Token.cs b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Token.cs index 414b48135..56c9301e4 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Token.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1Token.cs @@ -1,16 +1,15 @@ namespace UglyToad.PdfPig.Fonts.Type1.Parser { using System; - using System.Collections.Generic; using System.Globalization; - internal class Type1DataToken : Type1Token + internal sealed class Type1DataToken : Type1Token { - public IReadOnlyList Data { get; } + public ReadOnlyMemory Data { get; } public override bool IsPrivateDictionary { get; } = false; - public Type1DataToken(TokenType type, IReadOnlyList data) : base(string.Empty, type) + public Type1DataToken(TokenType type, ReadOnlyMemory data) : base(string.Empty, type) { if (type != TokenType.Charstring) { @@ -22,7 +21,7 @@ public Type1DataToken(TokenType type, IReadOnlyList data) : base(string.Em public override string ToString() { - return $"Token[type = {Type}, data = {Data.Count} bytes]"; + return $"Token[type = {Type}, data = {Data.Length} bytes]"; } } diff --git a/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs b/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs index 8493de780..0f502127d 100644 --- a/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs +++ b/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs @@ -35,7 +35,7 @@ private static byte[] HexToBytes(string hex) var result = new byte[hex.Length / 2]; for (var i = 0; i < hex.Length; i += 2) { - result[i / 2] = HexToken.Convert(hex[i], hex[i + 1]); + result[i / 2] = HexToken.ConvertPair(hex[i], hex[i + 1]); } return result; diff --git a/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs b/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs index d9a90154d..f73afafea 100644 --- a/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs @@ -162,7 +162,7 @@ public void Issue443() const string hex = @"00 0F 4A 43 42 31 33 36 36 31 32 32 37 2E 70 64 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 44 46 20 43 41 52 4F 01 00 FF FF FF FF 00 00 00 00 00 04 DF 28 00 00 00 00 AF 51 7E 82 AF 52 D7 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 81 03 0D 00 00 25 50 44 46 2D 31 2E 31 0A 25 E2 E3 CF D3 0D 0A 31 20 30 20 6F 62 6A"; - var bytes = hex.Split(' ').Where(x => x.Length > 0).Select(x => HexToken.Convert(x[0], x[1])); + var bytes = hex.Split(' ').Where(x => x.Length > 0).Select(x => HexToken.ConvertPair(x[0], x[1])); var str = OtherEncodings.BytesAsLatin1String(bytes.ToArray()); diff --git a/src/UglyToad.PdfPig.Tests/Util/OtherEncodingsTests.cs b/src/UglyToad.PdfPig.Tests/Util/OtherEncodingsTests.cs index 0f013ebd6..dbe6c962b 100644 --- a/src/UglyToad.PdfPig.Tests/Util/OtherEncodingsTests.cs +++ b/src/UglyToad.PdfPig.Tests/Util/OtherEncodingsTests.cs @@ -4,18 +4,10 @@ public class OtherEncodingsTests { - [Fact] - public void BytesNullReturnsNullString() - { - var result = OtherEncodings.BytesAsLatin1String(null); - - Assert.Null(result); - } - [Fact] public void BytesEmptyReturnsEmptyString() { - var result = OtherEncodings.BytesAsLatin1String(new byte[0]); + var result = OtherEncodings.BytesAsLatin1String([]); Assert.Equal(string.Empty, result); } diff --git a/src/UglyToad.PdfPig.Tokenization/HexTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/HexTokenizer.cs index b461b2fcd..72fe06772 100644 --- a/src/UglyToad.PdfPig.Tokenization/HexTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/HexTokenizer.cs @@ -1,10 +1,9 @@ namespace UglyToad.PdfPig.Tokenization { - using System.Collections.Generic; using Core; using Tokens; - internal class HexTokenizer : ITokenizer + internal sealed class HexTokenizer : ITokenizer { public bool ReadsNextByte { get; } = false; @@ -16,8 +15,8 @@ public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken tok { return false; } - - var characters = new List(); + + using var charBuffer = new ArrayPoolBufferWriter(); while (inputBytes.MoveNext()) { @@ -38,10 +37,10 @@ public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken tok return false; } - characters.Add((char)current); + charBuffer.Write((char)current); } - token = new HexToken(characters); + token = new HexToken(charBuffer.WrittenSpan); return true; } diff --git a/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs index f7a1554b4..03cf97b7b 100644 --- a/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/NameTokenizer.cs @@ -1,7 +1,6 @@ namespace UglyToad.PdfPig.Tokenization { using System; - using System.Collections.Generic; using System.Text; using Core; using Tokens; diff --git a/src/UglyToad.PdfPig.Tokens/ArrayToken.cs b/src/UglyToad.PdfPig.Tokens/ArrayToken.cs index 02da64f30..5e64ff63f 100644 --- a/src/UglyToad.PdfPig.Tokens/ArrayToken.cs +++ b/src/UglyToad.PdfPig.Tokens/ArrayToken.cs @@ -10,7 +10,7 @@ /// PDF arrays may be heterogeneous; that is, an array's elements may be any combination of numbers, strings, /// dictionaries, or any other objects, including other arrays. /// - public class ArrayToken : IDataToken> + public sealed class ArrayToken : IDataToken> { /// /// The tokens contained in this array. diff --git a/src/UglyToad.PdfPig.Tokens/HexToken.cs b/src/UglyToad.PdfPig.Tokens/HexToken.cs index aea5c6e94..e17bf6ee8 100644 --- a/src/UglyToad.PdfPig.Tokens/HexToken.cs +++ b/src/UglyToad.PdfPig.Tokens/HexToken.cs @@ -2,16 +2,14 @@ namespace UglyToad.PdfPig.Tokens { using System; using System.Collections.Generic; - using System.Linq; using System.Text; /// /// A token containing string data where the string is encoded as hexadecimal. /// - public class HexToken : IDataToken + public sealed class HexToken : IDataToken { - private static readonly Dictionary HexMap = new Dictionary - { + private static readonly Dictionary HexMap = new() { {'0', 0x00 }, {'1', 0x01 }, {'2', 0x02 }, @@ -42,29 +40,37 @@ public class HexToken : IDataToken /// public string Data { get; } + private readonly byte[] _bytes; + /// /// The bytes of the hex data. /// - public IReadOnlyList Bytes { get; } + public ReadOnlySpan Bytes => _bytes; + + /// + /// The memory of the hex data. + /// + public ReadOnlyMemory Memory => _bytes; /// /// Create a new from the provided hex characters. /// /// A set of hex characters 0-9, A - F, a - f representing a string. - public HexToken(IReadOnlyList characters) + public HexToken(ReadOnlySpan characters) { if (characters == null) { throw new ArgumentNullException(nameof(characters)); } - var bytes = new List(); + var bytes = new byte[characters.Length / 2]; + int index = 0; - for (var i = 0; i < characters.Count; i += 2) + for (var i = 0; i < characters.Length; i += 2) { char high = characters[i]; char low; - if (i == characters.Count - 1) + if (i == characters.Length - 1) { low = '0'; } @@ -73,14 +79,14 @@ public HexToken(IReadOnlyList characters) low = characters[i + 1]; } - var b = Convert(high, low); - bytes.Add(b); + var b = ConvertPair(high, low); + bytes[index++] = b; } // Handle UTF-16BE format strings. - if (bytes.Count >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) + if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF) { - Data = Encoding.BigEndianUnicode.GetString(bytes.ToArray(), 2, bytes.Count - 2); + Data = Encoding.BigEndianUnicode.GetString(bytes, 2, bytes.Length - 2); } else { @@ -97,7 +103,7 @@ public HexToken(IReadOnlyList characters) Data = builder.ToString(); } - Bytes = bytes; + _bytes = bytes; } /// @@ -106,7 +112,7 @@ public HexToken(IReadOnlyList characters) /// The high nibble. /// The low nibble. /// The byte. - public static byte Convert(char high, char low) + public static byte ConvertPair(char high, char low) { var highByte = HexMap[high]; var lowByte = HexMap[low]; @@ -129,7 +135,7 @@ public static int ConvertHexBytesToInt(HexToken token) var bytes = token.Bytes; var value = bytes[0] & 0xFF; - if (bytes.Count == 2) + if (bytes.Length == 2) { value <<= 8; value += bytes[1] & 0xFF; @@ -159,7 +165,11 @@ public bool Equals(IToken obj) /// public string GetHexString() { +#if NET8_0_OR_GREATER + return Convert.ToHexString(Bytes); +#else return BitConverter.ToString(Bytes.ToArray()).Replace("-", string.Empty); +#endif } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tokens/UglyToad.PdfPig.Tokens.csproj b/src/UglyToad.PdfPig.Tokens/UglyToad.PdfPig.Tokens.csproj index d3a3ea1a5..85d36b512 100644 --- a/src/UglyToad.PdfPig.Tokens/UglyToad.PdfPig.Tokens.csproj +++ b/src/UglyToad.PdfPig.Tokens/UglyToad.PdfPig.Tokens.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net462;net471;net6.0;net8.0 12 @@ -15,6 +15,9 @@ + + + diff --git a/src/UglyToad.PdfPig/Content/Word.cs b/src/UglyToad.PdfPig/Content/Word.cs index 1a27d477e..7d157fba8 100644 --- a/src/UglyToad.PdfPig/Content/Word.cs +++ b/src/UglyToad.PdfPig/Content/Word.cs @@ -67,30 +67,13 @@ public Word(IReadOnlyList letters) } } - Tuple data; - switch (tempTextOrientation) - { - case TextOrientation.Horizontal: - data = GetBoundingBoxH(letters); - break; - - case TextOrientation.Rotate180: - data = GetBoundingBox180(letters); - break; - - case TextOrientation.Rotate90: - data = GetBoundingBox90(letters); - break; - - case TextOrientation.Rotate270: - data = GetBoundingBox270(letters); - break; - - case TextOrientation.Other: - default: - data = GetBoundingBoxOther(letters); - break; - } + var data = tempTextOrientation switch { + TextOrientation.Horizontal => GetBoundingBoxH(letters), + TextOrientation.Rotate180 => GetBoundingBox180(letters), + TextOrientation.Rotate90 => GetBoundingBox90(letters), + TextOrientation.Rotate270 => GetBoundingBox270(letters), + _ => GetBoundingBoxOther(letters), + }; Text = data.Item1; BoundingBox = data.Item2; @@ -100,7 +83,7 @@ public Word(IReadOnlyList letters) } #region Bounding box - private Tuple GetBoundingBoxH(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBoxH(IReadOnlyList letters) { var builder = new StringBuilder(); @@ -136,10 +119,10 @@ private Tuple GetBoundingBoxH(IReadOnlyList letter } } - return new Tuple(builder.ToString(), new PdfRectangle(blX, blY, trX, trY)); + return new(builder.ToString(), new PdfRectangle(blX, blY, trX, trY)); } - private Tuple GetBoundingBox180(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBox180(IReadOnlyList letters) { var builder = new StringBuilder(); @@ -175,10 +158,10 @@ private Tuple GetBoundingBox180(IReadOnlyList lett } } - return new Tuple(builder.ToString(), new PdfRectangle(blX, blY, trX, trY)); + return (builder.ToString(), new PdfRectangle(blX, blY, trX, trY)); } - private Tuple GetBoundingBox90(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBox90(IReadOnlyList letters) { var builder = new StringBuilder(); @@ -214,12 +197,12 @@ private Tuple GetBoundingBox90(IReadOnlyList lette } } - return new Tuple(builder.ToString(), new PdfRectangle( + return new (builder.ToString(), new PdfRectangle( new PdfPoint(t, l), new PdfPoint(t, r), new PdfPoint(b, l), new PdfPoint(b, r))); } - private Tuple GetBoundingBox270(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBox270(IReadOnlyList letters) { var builder = new StringBuilder(); @@ -255,12 +238,12 @@ private Tuple GetBoundingBox270(IReadOnlyList lett } } - return new Tuple(builder.ToString(), new PdfRectangle( + return new(builder.ToString(), new PdfRectangle( new PdfPoint(t, l), new PdfPoint(t, r), new PdfPoint(b, l), new PdfPoint(b, r))); } - private Tuple GetBoundingBoxOther(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBoxOther(IReadOnlyList letters) { var builder = new StringBuilder(); for (var i = 0; i < letters.Count; i++) @@ -270,7 +253,7 @@ private Tuple GetBoundingBoxOther(IReadOnlyList le if (letters.Count == 1) { - return new Tuple(builder.ToString(), letters[0].GlyphRectangle); + return new(builder.ToString(), letters[0].GlyphRectangle); } else { @@ -367,7 +350,7 @@ private Tuple GetBoundingBoxOther(IReadOnlyList le obb = obb3; } - return new Tuple(builder.ToString(), obb); + return new(builder.ToString(), obb); } } #endregion @@ -379,7 +362,10 @@ private Tuple GetBoundingBoxOther(IReadOnlyList le private static double BoundAngle180(double angle) { angle = (angle + 180) % 360; - if (angle < 0) angle += 360; + if (angle < 0) + { + angle += 360; + } return angle - 180; } diff --git a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs index 4f4a68df6..d735eb635 100644 --- a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs +++ b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs @@ -402,7 +402,7 @@ private IToken DecryptInternal(IndirectReference reference, IToken token) var decrypted = DecryptData(data, reference); - token = new HexToken(Hex.GetString(decrypted).ToCharArray()); + token = new HexToken(Hex.GetString(decrypted).AsSpan()); break; } diff --git a/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs b/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs index b1513ca1b..84aa9e0c6 100644 --- a/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs +++ b/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs @@ -2,7 +2,7 @@ { using System; using System.Collections.Generic; - using System.IO; + using Core; using Tokens; /// @@ -35,77 +35,74 @@ public byte[] Decode(IReadOnlyList input, DictionaryToken streamDictionary var index = 0; - using (var stream = new MemoryStream()) - using (var writer = new BinaryWriter(stream)) + using var writer = new ArrayPoolBufferWriter(); + + for (var i = 0; i < input.Count; i++) { - for (var i = 0; i < input.Count; i++) - { - var value = input[i]; - - if (IsWhiteSpace(value)) - { - continue; - } - - if (value == EndOfDataBytes[0]) - { - if (i == input.Count - 1 || input[i + 1] == EndOfDataBytes[1]) - { - if (index > 0) - { - WriteData(asciiBuffer, index, writer); - } - - index = 0; + var value = input[i]; - // The end - break; - } - - // TODO: this shouldn't be possible? - } + if (IsWhiteSpace(value)) + { + continue; + } - if (value == EmptyBlock) + if (value == EndOfDataBytes[0]) + { + if (i == input.Count - 1 || input[i + 1] == EndOfDataBytes[1]) { if (index > 0) { - throw new InvalidOperationException("Encountered z within a 5 character block"); - } - - for (int j = 0; j < 4; j++) - { - writer.Write((byte)0); + WriteData(asciiBuffer, index, writer); } index = 0; - // We've completed our block. + // The end + break; } - else + + // TODO: this shouldn't be possible? + } + + if (value == EmptyBlock) + { + if (index > 0) { - asciiBuffer[index] = (byte) (value - Offset); - index++; + throw new InvalidOperationException("Encountered z within a 5 character block"); } - if (index == 5) + for (int j = 0; j < 4; j++) { - WriteData(asciiBuffer, index, writer); - index = 0; + writer.Write(0); } + + index = 0; + + // We've completed our block. + } + else + { + asciiBuffer[index] = (byte) (value - Offset); + index++; } - if (index > 0) + if (index == 5) { WriteData(asciiBuffer, index, writer); + index = 0; } + } - writer.Flush(); - - return stream.ToArray(); + if (index > 0) + { + WriteData(asciiBuffer, index, writer); } + + return writer.WrittenSpan.ToArray(); + } - private static void WriteData(byte[] ascii, int index, BinaryWriter writer) + private static void WriteData(Span ascii, int index, ArrayPoolBufferWriter writer) { if (index < 2) { diff --git a/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs index 3311b0380..0d0d745af 100644 --- a/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs @@ -382,10 +382,10 @@ public virtual void ShowPositionedText(IReadOnlyList tokens) } else { - IReadOnlyList bytes; + byte[] bytes; if (token is HexToken hex) { - bytes = hex.Bytes; + bytes = [.. hex.Bytes]; } else { diff --git a/src/UglyToad.PdfPig/Images/Png/Palette.cs b/src/UglyToad.PdfPig/Images/Png/Palette.cs index 052499fa4..24ae2a3df 100644 --- a/src/UglyToad.PdfPig/Images/Png/Palette.cs +++ b/src/UglyToad.PdfPig/Images/Png/Palette.cs @@ -1,6 +1,8 @@ namespace UglyToad.PdfPig.Images.Png { - internal class Palette + using System; + + internal sealed class Palette { public bool HasAlphaValues { get; private set; } @@ -9,7 +11,7 @@ internal class Palette /// /// Creates a palette object. Input palette data length from PLTE chunk must be a multiple of 3. /// - public Palette(byte[] data) + public Palette(ReadOnlySpan data) { Data = new byte[data.Length * 4 / 3]; var dataIndex = 0; diff --git a/src/UglyToad.PdfPig/Outline/BookmarkNode.cs b/src/UglyToad.PdfPig/Outline/BookmarkNode.cs index 8e507fef3..53753b820 100644 --- a/src/UglyToad.PdfPig/Outline/BookmarkNode.cs +++ b/src/UglyToad.PdfPig/Outline/BookmarkNode.cs @@ -1,6 +1,5 @@ namespace UglyToad.PdfPig.Outline { - using Destinations; using System; using System.Collections.Generic; diff --git a/src/UglyToad.PdfPig/Parser/FileStructure/CrossReferenceTableParser.cs b/src/UglyToad.PdfPig/Parser/FileStructure/CrossReferenceTableParser.cs index 875ad1174..2f926880a 100644 --- a/src/UglyToad.PdfPig/Parser/FileStructure/CrossReferenceTableParser.cs +++ b/src/UglyToad.PdfPig/Parser/FileStructure/CrossReferenceTableParser.cs @@ -1,6 +1,6 @@ namespace UglyToad.PdfPig.Parser.FileStructure { - using System.Collections.Generic; + using System; using System.Linq; using CrossReference; using Core; @@ -55,8 +55,9 @@ public static CrossReferenceTablePart Parse(ISeekableTokenScanner scanner, long scanner.RegisterCustomTokenizer((byte)'\r', tokenizer); scanner.RegisterCustomTokenizer((byte)'\n', tokenizer); + using var tokens = new ArrayPoolBufferWriter(); + var readingLine = false; - var tokens = new List(); var count = 0; while (scanner.MoveNext()) { @@ -69,9 +70,9 @@ public static CrossReferenceTablePart Parse(ISeekableTokenScanner scanner, long readingLine = false; - count = ProcessTokens(tokens, builder, isLenientParsing, count, ref definition); + count = ProcessTokens(tokens.WrittenSpan, builder, isLenientParsing, count, ref definition); - tokens.Clear(); + tokens.Reset(); continue; } @@ -89,12 +90,12 @@ public static CrossReferenceTablePart Parse(ISeekableTokenScanner scanner, long } readingLine = true; - tokens.Add(scanner.CurrentToken); + tokens.Write(scanner.CurrentToken); } - if (tokens.Count > 0) + if (tokens.WrittenCount > 0) { - ProcessTokens(tokens, builder, isLenientParsing, count, ref definition); + ProcessTokens(tokens.WrittenSpan, builder, isLenientParsing, count, ref definition); } scanner.DeregisterCustomTokenizer(tokenizer); @@ -105,19 +106,17 @@ public static CrossReferenceTablePart Parse(ISeekableTokenScanner scanner, long return builder.Build(); } - private static int ProcessTokens(List tokens, CrossReferenceTablePartBuilder builder, bool isLenientParsing, + private static int ProcessTokens(ReadOnlySpan tokens, CrossReferenceTablePartBuilder builder, bool isLenientParsing, int objectCount, ref TableSubsectionDefinition definition) { - string GetErrorMessage() + static string GetErrorMessage(ReadOnlySpan tokens) { - var representation = "Invalid line format in xref table: [" + string.Join(", ", tokens.Select(x => x.ToString())) + "]"; - - return representation; + return "Invalid line format in xref table: [" + string.Join(", ", tokens.ToArray().Select(x => x.ToString())) + "]"; } if (objectCount == definition.Count) { - if (tokens.Count == 2) + if (tokens.Length == 2) { if (tokens[0] is NumericToken newFirstObjectToken && tokens[1] is NumericToken newObjectCountToken) { @@ -130,17 +129,17 @@ string GetErrorMessage() throw new PdfDocumentFormatException($"Found a line with 2 unexpected entries in the cross reference table: {tokens[0]}, {tokens[1]}."); } - if (tokens.Count <= 2) + if (tokens.Length <= 2) { if (!isLenientParsing) { - throw new PdfDocumentFormatException(GetErrorMessage()); + throw new PdfDocumentFormatException(GetErrorMessage(tokens)); } return objectCount; } - var lastToken = tokens[tokens.Count - 1]; + var lastToken = tokens[tokens.Length - 1]; if (lastToken is OperatorToken operatorToken) { @@ -153,7 +152,7 @@ string GetErrorMessage() { if (!isLenientParsing) { - throw new PdfDocumentFormatException(GetErrorMessage()); + throw new PdfDocumentFormatException(GetErrorMessage(tokens)); } return objectCount; @@ -170,7 +169,7 @@ string GetErrorMessage() { if (!isLenientParsing) { - throw new PdfDocumentFormatException(GetErrorMessage()); + throw new PdfDocumentFormatException(GetErrorMessage(tokens)); } } diff --git a/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs b/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs index 8f9bef75c..5fc5b9ada 100644 --- a/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs +++ b/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs @@ -24,7 +24,7 @@ public static int ReadGenerationNumber(IInputBytes bytes) int result = ReadHelper.ReadInt(bytes); if (result < 0 || result > GenerationNumberThreshold) { - throw new FormatException("Generation Number '" + result + "' has more than 5 digits"); + throw new FormatException($"Generation Number '{result}' has more than 5 digits"); } return result; diff --git a/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs b/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs index d107ba6dc..a57a79920 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs @@ -152,7 +152,7 @@ public int ReadCode(IInputBytes bytes) { var data = new byte[minCodeLength]; bytes.Read(data); - return data.ToInt(minCodeLength); + return ((ReadOnlySpan)data).Slice(0, minCodeLength).ToInt(); } byte[] result = new byte[maxCodeLength]; diff --git a/src/UglyToad.PdfPig/PdfFonts/Cmap/CMapUtils.cs b/src/UglyToad.PdfPig/PdfFonts/Cmap/CMapUtils.cs index 4fead5045..d5c0fd6fc 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Cmap/CMapUtils.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Cmap/CMapUtils.cs @@ -1,13 +1,14 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap { + using System; using System.Collections.Generic; internal static class CMapUtils { - public static int ToInt(this IReadOnlyList data, int length) + public static int ToInt(this ReadOnlySpan data) { int code = 0; - for (int i = 0; i < length; ++i) + for (int i = 0; i < data.Length; ++i) { code <<= 8; code |= (data[i] & 0xFF); @@ -15,8 +16,7 @@ public static int ToInt(this IReadOnlyList data, int length) return code; } - public static void PutAll(this Dictionary target, - IReadOnlyDictionary source) + public static void PutAll(this Dictionary target, IReadOnlyDictionary source) { foreach (var pair in source) { @@ -24,4 +24,4 @@ public static void PutAll(this Dictionary target, } } } -} +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/PdfFonts/Cmap/CharacterMapBuilder.cs b/src/UglyToad.PdfPig/PdfFonts/Cmap/CharacterMapBuilder.cs index d402b4ae3..5fcbfea27 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Cmap/CharacterMapBuilder.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Cmap/CharacterMapBuilder.cs @@ -3,6 +3,7 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap { using Core; + using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -66,14 +67,14 @@ internal class CharacterMapBuilder public Dictionary BaseFontCharacterMap { get; } = new Dictionary(); - public void AddBaseFontCharacter(IReadOnlyList bytes, IReadOnlyList value) + public void AddBaseFontCharacter(ReadOnlySpan bytes, ReadOnlySpan value) { - AddBaseFontCharacter(bytes, CreateStringFromBytes(value.ToArray())); + AddBaseFontCharacter(bytes, CreateStringFromBytes(value)); } - public void AddBaseFontCharacter(IReadOnlyList bytes, string value) + public void AddBaseFontCharacter(ReadOnlySpan bytes, string value) { - var code = GetCodeFromArray(bytes, bytes.Count); + var code = GetCodeFromArray(bytes); BaseFontCharacterMap[code] = value; } @@ -134,17 +135,13 @@ private static IReadOnlyList Combine(IReadOnlyList a, IReadOnlyList return a; } - var result = new List(a); - - result.AddRange(b); - - return result; + return [.. a, .. b]; } - private int GetCodeFromArray(IReadOnlyList data, int length) + private int GetCodeFromArray(ReadOnlySpan data) { int code = 0; - for (int i = 0; i < length; i++) + for (int i = 0; i < data.Length; i++) { code <<= 8; code |= (data[i] + 256) % 256; @@ -152,7 +149,7 @@ private int GetCodeFromArray(IReadOnlyList data, int length) return code; } - private static string CreateStringFromBytes(byte[] bytes) + private static string CreateStringFromBytes(ReadOnlySpan bytes) { return bytes.Length == 1 ? OtherEncodings.BytesAsLatin1String(bytes) diff --git a/src/UglyToad.PdfPig/PdfFonts/Cmap/CodespaceRange.cs b/src/UglyToad.PdfPig/PdfFonts/Cmap/CodespaceRange.cs index 77acc16b6..73d67ca1c 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Cmap/CodespaceRange.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Cmap/CodespaceRange.cs @@ -1,7 +1,6 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap { using System; - using System.Collections.Generic; /// /// A codespace range is specified by a pair of codes of some particular length giving the lower and upper bounds of that range. @@ -11,12 +10,12 @@ internal class CodespaceRange /// /// The lower-bound of this range. /// - public IReadOnlyList Start { get; } + public ReadOnlyMemory Start { get; } /// /// The upper-bound of this range. /// - public IReadOnlyList End { get; } + public ReadOnlyMemory End { get; } /// /// The lower-bound of this range as an integer. @@ -36,13 +35,13 @@ internal class CodespaceRange /// /// Creates a new instance of . /// - public CodespaceRange(IReadOnlyList start, IReadOnlyList end) + public CodespaceRange(ReadOnlyMemory start, ReadOnlyMemory end) { Start = start; End = end; - StartInt = start.ToInt(start.Count); - EndInt = end.ToInt(end.Count); - CodeLength = start.Count; + StartInt = start.Span.ToInt(); + EndInt = end.Span.ToInt(); + CodeLength = start.Length; } /// @@ -74,7 +73,7 @@ public bool IsFullMatch(byte[] code, int codeLength) return false; } - var value = code.ToInt(codeLength); + var value = ((ReadOnlySpan)code).Slice(0, codeLength).ToInt(); if (value >= StartInt && value <= EndInt) { return true; diff --git a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs index 1febe4269..92bfe130e 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs @@ -34,7 +34,7 @@ public void Parse(NumericToken numberOfOperations, ITokenScanner scanner, Charac throw new InvalidFontFormatException("bfrange ended unexpectedly after the high source code."); } - List? destinationBytes = null; + byte[]? destinationBytes = null; ArrayToken? destinationArray = null; switch (scanner.CurrentToken) @@ -43,7 +43,7 @@ public void Parse(NumericToken numberOfOperations, ITokenScanner scanner, Charac destinationArray = arrayToken; break; case HexToken hexToken: - destinationBytes = hexToken.Bytes.ToList(); + destinationBytes = [.. hexToken.Bytes]; break; case NumericToken _: throw new NotImplementedException("From the spec it seems this possible but the meaning is unclear..."); @@ -52,7 +52,7 @@ public void Parse(NumericToken numberOfOperations, ITokenScanner scanner, Charac } var done = false; - var startCode = new List(lowSourceCode.Bytes); + var startCode = lowSourceCode.Bytes.ToArray(); var endCode = highSourceCode.Bytes; if (destinationArray != null) @@ -76,7 +76,7 @@ public void Parse(NumericToken numberOfOperations, ITokenScanner scanner, Charac builder.AddBaseFontCharacter(startCode, hex.Bytes); } - Increment(startCode, startCode.Count - 1); + Increment(startCode, startCode.Length - 1); arrayIndex++; } @@ -93,14 +93,14 @@ public void Parse(NumericToken numberOfOperations, ITokenScanner scanner, Charac builder.AddBaseFontCharacter(startCode, destinationBytes!); - Increment(startCode, startCode.Count - 1); + Increment(startCode, startCode.Length - 1); - Increment(destinationBytes!, destinationBytes!.Count - 1); + Increment(destinationBytes!, destinationBytes!.Length - 1); } } } - private static void Increment(IList data, int position) + private static void Increment(Span data, int position) { if (position > 0 && (data[position] & 0xFF) == 255) { @@ -113,9 +113,9 @@ private static void Increment(IList data, int position) } } - private static int Compare(IReadOnlyList first, IReadOnlyList second) + private static int Compare(ReadOnlySpan first, ReadOnlySpan second) { - for (var i = 0; i < first.Count; i++) + for (var i = 0; i < first.Length; i++) { if (first[i] == second[i]) { diff --git a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs index 767492566..4f885d57d 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs @@ -24,7 +24,7 @@ public void Parse(NumericToken numeric, ITokenScanner scanner, CharacterMapBuild throw new InvalidOperationException("The destination token in a line for Cid Character should be an integer, instead it was: " + scanner.CurrentToken); } - var sourceInteger = sourceCode.Bytes.ToInt(sourceCode.Bytes.Count); + var sourceInteger = sourceCode.Bytes.ToInt(); var mapping = new CidCharacterMapping(sourceInteger, destinationCode.Int); results.Add(mapping); diff --git a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs index 03a49caed..f7c784b23 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs @@ -44,7 +44,7 @@ 3 begincodespacerange throw new InvalidOperationException("Codespace range contains an unexpected token: " + tokenScanner.CurrentToken); } - ranges.Add(new CodespaceRange(start.Bytes, end.Bytes)); + ranges.Add(new CodespaceRange(start.Memory, end.Memory)); } builder.CodespaceRanges = ranges; diff --git a/src/UglyToad.PdfPig/Polyfills/EncodingExtensions.cs b/src/UglyToad.PdfPig/Polyfills/EncodingExtensions.cs new file mode 100644 index 000000000..9c4e7a12d --- /dev/null +++ b/src/UglyToad.PdfPig/Polyfills/EncodingExtensions.cs @@ -0,0 +1,20 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 + +namespace System.Text; + +internal static class EncodingExtensions +{ + public static string GetString(this Encoding encoding, ReadOnlySpan bytes) + { + if (bytes.IsEmpty) + { + return string.Empty; + } + + // NOTE: this can be made allocation free by introducing unsafe + + return encoding.GetString(bytes.ToArray()); + } +} + +#endif \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs b/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs index 899e33527..3ead4d45d 100644 --- a/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs +++ b/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs @@ -825,7 +825,7 @@ private IReadOnlyList ParseObjectStream(StreamToken stream, long of var scanner = new CoreTokenScanner(bytes, true, useLenientParsing: parsingOptions.UseLenientParsing); - var objects = new List>(); + var objects = new List<(long, long)>(); for (var i = 0; i < numberOfObjects.Int; i++) { @@ -834,7 +834,7 @@ private IReadOnlyList ParseObjectStream(StreamToken stream, long of scanner.MoveNext(); var byteOffset = (NumericToken)scanner.CurrentToken; - objects.Add(Tuple.Create(objectNumber.Long, byteOffset.Long)); + objects.Add((objectNumber.Long, byteOffset.Long)); } var results = new List(); diff --git a/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs b/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs index e05016e0b..84d09be08 100644 --- a/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs +++ b/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs @@ -55,8 +55,7 @@ public static ColorSpaceDetails GetColorSpaceDetails(ColorSpace? colorSpace, var colorSpaceDetails = GetColorSpaceDetails(colorSpace, imageDictionary.Without(NameToken.Filter).Without(NameToken.F), scanner, resourceStore, filterProvider, true); - var decodeRaw = imageDictionary.GetObjectOrDefault(NameToken.Decode, NameToken.D) as ArrayToken - ?? new ArrayToken(Array.Empty()); + var decodeRaw = imageDictionary.GetObjectOrDefault(NameToken.Decode, NameToken.D) as ArrayToken ?? new ArrayToken([]); var decode = decodeRaw.Data.OfType().Select(x => x.Double).ToArray(); return IndexedColorSpaceDetails.Stencil(colorSpaceDetails, decode); @@ -341,7 +340,7 @@ public static ColorSpaceDetails GetColorSpaceDetails(ColorSpace? colorSpace, if (DirectObjectFinder.TryGet(fourth, scanner, out HexToken? tableHexToken)) { - tableBytes = tableHexToken.Bytes; + tableBytes = [.. tableHexToken.Bytes]; } else if (DirectObjectFinder.TryGet(fourth, scanner, out StreamToken? tableStreamToken)) { diff --git a/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs b/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs index 38088d73c..b46ca1845 100644 --- a/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs @@ -16,7 +16,7 @@ internal static class ToUnicodeCMapBuilder private static readonly TokenWriter TokenWriter = new TokenWriter(); - public static IReadOnlyList ConvertToCMapStream(IReadOnlyDictionary unicodeToCharacterCode) + public static byte[] ConvertToCMapStream(IReadOnlyDictionary unicodeToCharacterCode) { using (var memoryStream = new MemoryStream()) { diff --git a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs index 33880945b..210d1209f 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -702,7 +702,9 @@ public AddedImage AddPng(Stream pngStream, PdfRectangle placementRectangle = def var png = Png.Open(pngStream); if (placementRectangle.Equals(default(PdfRectangle))) + { placementRectangle = new PdfRectangle(0, 0, png.Width, png.Height); + } byte[] data; var pixelBuffer = new byte[3];