diff --git a/src/UglyToad.PdfPig.Core/ArrayPoolBufferWriter.cs b/src/UglyToad.PdfPig.Core/ArrayPoolBufferWriter.cs new file mode 100644 index 00000000..1abd1172 --- /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 55a43afb..fc5f743c 100644 --- a/src/UglyToad.PdfPig.Core/OctalHelpers.cs +++ b/src/UglyToad.PdfPig.Core/OctalHelpers.cs @@ -42,7 +42,7 @@ /// /// 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 18f63ad2..f4dbe3f6 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 @@ /// /// 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 e58ad0f7..64080f71 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 @@ /// 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 00000000..59831580 --- /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 fe205bfd..07a07e07 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 const byte AsciiCarriageReturn = 13; - private static readonly HashSet EndOfNameCharacters = new HashSet - { + private static readonly HashSet EndOfNameCharacters = + [ ' ', AsciiCarriageReturn, AsciiLineFeed, @@ -35,7 +39,7 @@ '(', 0, '\f' - }; + ]; private static readonly int MaximumNumberStringLength = long.MaxValue.ToString("D").Length; @@ -269,7 +273,11 @@ /// 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 IsValidUtf8(byte[] input) { +#if NET8_0_OR_GREATER + return Utf8.IsValid(input); +#else try { var d = Encoding.UTF8.GetDecoder(); @@ -290,6 +301,7 @@ { 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 3e7a34c2..41665c7a 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 104aab19..592a4eb5 100644 --- a/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs +++ b/src/UglyToad.PdfPig.DocumentLayoutAnalysis/TextEdgesExtractor.cs @@ -17,12 +17,12 @@ /// /// 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 f1b5cde5..f86ea97c 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 @@ return new Type1CharStrings(charStringResults, charStringIndexToName, subroutineResults); } - private static 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 @@ 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 @@ 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 7f6d5148..7c0c8f0c 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 @@ 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 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 cf6bb28b..f2403e2a 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1EncryptedPortionParser.cs @@ -14,7 +14,7 @@ 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 @@ 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 @@ 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 @@ /// 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 @@ 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 @@ if (offset == 1) { - result.Add(HexToken.Convert(last, c)); + result[index++] = HexToken.ConvertPair(last, c); offset = 0; } else @@ -371,7 +372,7 @@ 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 @@ 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 @@ 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 @@ 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 a6be5f62..5a001199 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/Parser/Type1FontParser.cs @@ -32,7 +32,7 @@ // 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 @@ 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 @@ { for (int i = 0; i < offset; i++) { - tempEexecPortion.Add((byte)ClearToMark[i]); + tempEexecPortion.Write((byte)ClearToMark[i]); } } @@ -117,7 +117,7 @@ continue; } - tempEexecPortion.Add(inputBytes.CurrentByte); + tempEexecPortion.Write(inputBytes.CurrentByte); } } else @@ -131,7 +131,7 @@ 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 414b4813..56c9301e 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 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 8493de78..0f502127 100644 --- a/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs +++ b/src/UglyToad.PdfPig.Tests/Encryption/RC4Tests.cs @@ -35,7 +35,7 @@ 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 d9a90154..f73afafe 100644 --- a/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs +++ b/src/UglyToad.PdfPig.Tests/Parser/Parts/FileHeaderParserTests.cs @@ -162,7 +162,7 @@ three %PDF-1.6"; 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 0f013ebd..dbe6c962 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 b461b2fc..72fe0677 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 @@ { return false; } - - var characters = new List(); + + using var charBuffer = new ArrayPoolBufferWriter(); while (inputBytes.MoveNext()) { @@ -38,10 +37,10 @@ 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 f7a1554b..03cf97b7 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 02da64f3..5e64ff63 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 aea5c6e9..e17bf6ee 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 @@ namespace UglyToad.PdfPig.Tokens /// 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 @@ namespace UglyToad.PdfPig.Tokens 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 @@ namespace UglyToad.PdfPig.Tokens Data = builder.ToString(); } - Bytes = bytes; + _bytes = bytes; } /// @@ -106,7 +112,7 @@ namespace UglyToad.PdfPig.Tokens /// 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 @@ namespace UglyToad.PdfPig.Tokens 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 @@ namespace UglyToad.PdfPig.Tokens /// 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 d3a3ea1a..85d36b51 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 1a27d477..7d157fba 100644 --- a/src/UglyToad.PdfPig/Content/Word.cs +++ b/src/UglyToad.PdfPig/Content/Word.cs @@ -67,30 +67,13 @@ } } - 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 @@ } #region Bounding box - private Tuple GetBoundingBoxH(IReadOnlyList letters) + private (string, PdfRectangle) GetBoundingBoxH(IReadOnlyList letters) { var builder = new StringBuilder(); @@ -136,10 +119,10 @@ } } - 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 @@ } } - 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 @@ } } - 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 @@ } } - 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 @@ if (letters.Count == 1) { - return new Tuple(builder.ToString(), letters[0].GlyphRectangle); + return new(builder.ToString(), letters[0].GlyphRectangle); } else { @@ -367,7 +350,7 @@ obb = obb3; } - return new Tuple(builder.ToString(), obb); + return new(builder.ToString(), obb); } } #endregion @@ -379,7 +362,10 @@ 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 4f4a68df..d735eb63 100644 --- a/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs +++ b/src/UglyToad.PdfPig/Encryption/EncryptionHandler.cs @@ -402,7 +402,7 @@ namespace UglyToad.PdfPig.Encryption 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 b1513ca1..84aa9e0c 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 @@ 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)) { - var value = input[i]; + continue; + } - 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; - - // The end - break; - } - - // TODO: this shouldn't be possible? - } - - 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. - } - else - { - asciiBuffer[index] = (byte) (value - Offset); - index++; + // The end + break; } - if (index == 5) - { - WriteData(asciiBuffer, index, writer); - index = 0; - } + // TODO: this shouldn't be possible? } - if (index > 0) + if (value == EmptyBlock) + { + if (index > 0) + { + throw new InvalidOperationException("Encountered z within a 5 character block"); + } + + for (int j = 0; j < 4; j++) + { + writer.Write(0); + } + + index = 0; + + // We've completed our block. + } + else + { + asciiBuffer[index] = (byte) (value - Offset); + index++; + } + + 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 3311b038..0d0d745a 100644 --- a/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/BaseStreamProcessor.cs @@ -382,10 +382,10 @@ } 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 052499fa..24ae2a3d 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 @@ /// /// 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 8e507fef..53753b82 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 875ad117..2f926880 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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ { if (!isLenientParsing) { - throw new PdfDocumentFormatException(GetErrorMessage()); + throw new PdfDocumentFormatException(GetErrorMessage(tokens)); } return objectCount; @@ -170,7 +169,7 @@ { 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 8f9bef75..5fc5b9ad 100644 --- a/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs +++ b/src/UglyToad.PdfPig/Parser/Parts/ObjectHelper.cs @@ -24,7 +24,7 @@ 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 d107ba6d..a57a7992 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Cmap/CMap.cs @@ -152,7 +152,7 @@ { 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 4fead504..d5c0fd6f 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 @@ 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 @@ } } } -} +} \ 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 d402b4ae..5fcbfea2 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 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap 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 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap 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 @@ namespace UglyToad.PdfPig.PdfFonts.Cmap 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 77acc16b..73d67ca1 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 @@ /// /// 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 @@ /// /// 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 @@ 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 1febe426..92bfe130 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/BaseFontRangeParser.cs @@ -34,7 +34,7 @@ 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 @@ 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 @@ } 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 @@ builder.AddBaseFontCharacter(startCode, hex.Bytes); } - Increment(startCode, startCode.Count - 1); + Increment(startCode, startCode.Length - 1); arrayIndex++; } @@ -93,14 +93,14 @@ 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 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 76749256..4f885d57 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CidCharacterParser.cs @@ -24,7 +24,7 @@ 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 03a49cae..f7c784b2 100644 --- a/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs +++ b/src/UglyToad.PdfPig/PdfFonts/Parser/Parts/CodespaceRangeParser.cs @@ -44,7 +44,7 @@ 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 00000000..9c4e7a12 --- /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 899e3352..3ead4d45 100644 --- a/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs +++ b/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs @@ -825,7 +825,7 @@ 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 @@ 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 e05016e0..84d09be0 100644 --- a/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs +++ b/src/UglyToad.PdfPig/Util/ColorSpaceDetailsParser.cs @@ -55,8 +55,7 @@ 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 @@ 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 38088d73..b46ca184 100644 --- a/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/Fonts/ToUnicodeCMapBuilder.cs @@ -16,7 +16,7 @@ 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 33880945..210d1209 100644 --- a/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs +++ b/src/UglyToad.PdfPig/Writer/PdfPageBuilder.cs @@ -702,7 +702,9 @@ 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];