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];