Improve Code Quality (#818)

* Make AdobeFontMetricsLigature a struct

* Make AdobeFontMetricsCharacterSize a struct

* Eliminate allocation in CompactFontFormatData

* Pass TransformationMatrix  by reference

* Seal Encoding classes

* Make SubTableHeaderEntry a readonly struct

* Introduce StringSplitter and eliminate various allocations in GlyphListFactory

* Eliminate a few substring allocations

* Use char overload on StringBuilder

* Eliminate virtual calls on stringIndex

* Optimize ReadHelper ReadLong and ReadInt methods

* Add additional readonly annotations to PdfRectangle

* Optimize NameTokenizer

* Eliminate allocation in TrueTypeGlyphTableSubsetter

* Use empty arrays

* Eliminate allocations in OperationWriteHelper.WriteHex

* Use simplified DecryptCbc method on .NET 6+

* Fix windows-1252 encoding not working on net6.0 and 8.0

* Update int buffers to exact unsigned max length and eliminate additional byte allocation

* Fix typo

* Remove unused constant
This commit is contained in:
Jason Nelson
2024-04-18 11:58:40 -07:00
committed by GitHub
parent 0f7077b257
commit 1ef2e127a6
42 changed files with 326 additions and 167 deletions

View File

@@ -16,22 +16,22 @@
/// <summary>
/// Top left point of the rectangle.
/// </summary>
public PdfPoint TopLeft { get; }
public readonly PdfPoint TopLeft { get; }
/// <summary>
/// Top right point of the rectangle.
/// </summary>
public PdfPoint TopRight { get; }
public readonly PdfPoint TopRight { get; }
/// <summary>
/// Bottom right point of the rectangle.
/// </summary>
public PdfPoint BottomRight { get; }
public readonly PdfPoint BottomRight { get; }
/// <summary>
/// Bottom left point of the rectangle.
/// </summary>
public PdfPoint BottomLeft { get; }
public readonly PdfPoint BottomLeft { get; }
/// <summary>
/// Centroid point of the rectangle.

View File

@@ -1,8 +1,8 @@
namespace UglyToad.PdfPig.Core
{
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
#if NET8_0_OR_GREATER
@@ -41,8 +41,6 @@
'\f'
];
private static readonly int MaximumNumberStringLength = long.MaxValue.ToString("D").Length;
/// <summary>
/// Read a string from the input until a newline.
/// </summary>
@@ -134,7 +132,7 @@
/// </remarks>
public static bool IsWhitespace(byte c)
{
return c == 0 || c == 32 || c == AsciiLineFeed || c == AsciiCarriageReturn || c == 9 || c == 12;
return c is 0 or 32 or AsciiLineFeed or AsciiCarriageReturn or 9 or 12;
}
/// <summary>
@@ -198,25 +196,24 @@
public static long ReadLong(IInputBytes bytes)
{
SkipSpaces(bytes);
long retval;
StringBuilder longBuffer = ReadStringNumber(bytes);
Span<byte> buffer = stackalloc byte[19]; // max formatted uint64 length
try
ReadNumberAsUtf8Bytes(bytes, buffer, out int bytesRead);
ReadOnlySpan<byte> longBytes = buffer.Slice(0, bytesRead);
if (Utf8Parser.TryParse(longBytes, out long result, out _))
{
retval = long.Parse(longBuffer.ToString(), CultureInfo.InvariantCulture);
return result;
}
catch (FormatException e)
else
{
var bytesToReverse = OtherEncodings.StringAsLatin1Bytes(longBuffer.ToString());
bytes.Seek(bytes.CurrentOffset - bytesToReverse.Length);
bytes.Seek(bytes.CurrentOffset - bytesRead);
throw new InvalidOperationException($"Error: Expected a long type at offset {bytes.CurrentOffset}, instead got \'{longBuffer}\'", e);
throw new InvalidOperationException($"Error: Expected a long type at offset {bytes.CurrentOffset}, instead got \'{OtherEncodings.BytesAsLatin1String(longBytes)}\'");
}
return retval;
}
/// <summary>
/// Whether the given value is a digit or not.
@@ -231,28 +228,29 @@
/// </summary>
public static int ReadInt(IInputBytes bytes)
{
if (bytes == null)
if (bytes is null)
{
throw new ArgumentNullException(nameof(bytes));
}
SkipSpaces(bytes);
int result;
var intBuffer = ReadStringNumber(bytes);
Span<byte> buffer = stackalloc byte[10]; // max formatted uint32 length
try
ReadNumberAsUtf8Bytes(bytes, buffer, out int bytesRead);
var intBytes = buffer.Slice(0, bytesRead);
if (Utf8Parser.TryParse(intBytes, out int result, out _))
{
result = int.Parse(intBuffer.ToString(), CultureInfo.InvariantCulture);
return result;
}
catch (Exception e)
else
{
bytes.Seek(bytes.CurrentOffset - OtherEncodings.StringAsLatin1Bytes(intBuffer.ToString()).Length);
throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}", e);
bytes.Seek(bytes.CurrentOffset - bytesRead);
throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}, instead got \'{OtherEncodings.BytesAsLatin1String(intBytes)}\'");
}
return result;
}
/// <summary>
@@ -304,25 +302,26 @@
#endif
}
private static StringBuilder ReadStringNumber(IInputBytes reader)
private static void ReadNumberAsUtf8Bytes(IInputBytes reader, scoped Span<byte> buffer, out int bytesRead)
{
byte lastByte;
StringBuilder buffer = new StringBuilder();
int position = 0;
byte lastByte;
while (reader.MoveNext() && (lastByte = reader.CurrentByte) != ' ' &&
lastByte != AsciiLineFeed &&
lastByte != AsciiCarriageReturn &&
lastByte != 60 && //see sourceforge bug 1714707
lastByte != 60 && // see sourceforge bug 1714707
lastByte != '[' && // PDFBOX-1845
lastByte != '(' && // PDFBOX-2579
lastByte != 0)
{
buffer.Append((char)lastByte);
if (buffer.Length > MaximumNumberStringLength)
if (position >= buffer.Length)
{
throw new InvalidOperationException($"Number \'{buffer}\' is getting too long, stop reading at offset {reader.CurrentOffset}");
throw new InvalidOperationException($"Number \'{OtherEncodings.BytesAsLatin1String(buffer.Slice(0, position))}\' is getting too long, stop reading at offset {reader.CurrentOffset}");
}
buffer[position++] = lastByte;
}
if (!reader.IsAtEnd())
@@ -330,7 +329,7 @@
reader.Seek(reader.CurrentOffset - 1);
}
return buffer;
bytesRead = position;
}
}
}

View File

@@ -4,7 +4,7 @@
/// The x and y components of the width vector of the font's characters.
/// Presence implies that IsFixedPitch is true.
/// </summary>
public class AdobeFontMetricsCharacterSize
public readonly struct AdobeFontMetricsCharacterSize
{
/// <summary>
/// The horizontal width.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// A ligature in an Adobe Font Metrics individual character.
/// </summary>
public class AdobeFontMetricsLigature
public readonly struct AdobeFontMetricsLigature
{
/// <summary>
/// The character to join with to form a ligature.

View File

@@ -1,10 +1,8 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Core;
/// <summary>
/// Provides access to the raw bytes of this Compact Font Format file with utility methods for reading data types from it.
@@ -37,14 +35,7 @@
/// </summary>
public string ReadString(int length, Encoding encoding)
{
var bytes = new byte[length];
for (var i = 0; i < bytes.Length; i++)
{
bytes[i] = ReadByte();
}
return encoding.GetString(bytes);
return encoding.GetString(ReadSpan(length));
}
/// <summary>
@@ -86,6 +77,20 @@
return value;
}
internal ReadOnlySpan<byte> ReadSpan(int count)
{
if (Position + count >= dataBytes.Length)
{
throw new IndexOutOfRangeException($"Cannot read past end of data. Attempted to read to {Position + count} when the underlying data is {dataBytes.Length} bytes long.");
}
var result = dataBytes.Span.Slice(Position + 1, count);
Position += count;
return result;
}
/// <summary>
/// Read byte.
/// </summary>

View File

@@ -8,7 +8,7 @@
internal static class CompactFontFormatEncodingReader
{
public static Encoding ReadEncoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList<string> stringIndex)
public static Encoding ReadEncoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan<string> stringIndex)
{
if (data == null)
{
@@ -32,7 +32,7 @@
}
}
private static CompactFontFormatFormat0Encoding ReadFormat0Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList<string> stringIndex, byte format)
private static CompactFontFormatFormat0Encoding ReadFormat0Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan<string> stringIndex, byte format)
{
var numberOfCodes = data.ReadCard8();
@@ -45,7 +45,7 @@
values.Add((code, sid, str));
}
IReadOnlyList<CompactFontFormatBuiltInEncoding.Supplement> supplements = new List<CompactFontFormatBuiltInEncoding.Supplement>();
IReadOnlyList<CompactFontFormatBuiltInEncoding.Supplement> supplements = [];
if (HasSupplement(format))
{
supplements = ReadSupplement(data, stringIndex);
@@ -54,7 +54,7 @@
return new CompactFontFormatFormat0Encoding(values, supplements);
}
private static CompactFontFormatFormat1Encoding ReadFormat1Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, IReadOnlyList<string> stringIndex, byte format)
private static CompactFontFormatFormat1Encoding ReadFormat1Encoding(CompactFontFormatData data, ICompactFontFormatCharset charset, ReadOnlySpan<string> stringIndex, byte format)
{
var numberOfRanges = data.ReadCard8();
@@ -85,7 +85,7 @@
}
private static IReadOnlyList<CompactFontFormatBuiltInEncoding.Supplement> ReadSupplement(CompactFontFormatData dataInput,
IReadOnlyList<string> stringIndex)
ReadOnlySpan<string> stringIndex)
{
var numberOfSupplements = dataInput.ReadCard8();
var supplements = new CompactFontFormatBuiltInEncoding.Supplement[numberOfSupplements];
@@ -101,13 +101,13 @@
return supplements;
}
private static string ReadString(int index, IReadOnlyList<string> stringIndex)
private static string ReadString(int index, ReadOnlySpan<string> stringIndex)
{
if (index >= 0 && index <= 390)
{
return CompactFontFormatStandardStrings.GetName(index);
}
if (index - 391 < stringIndex.Count)
if (index - 391 < stringIndex.Length)
{
return stringIndex[index - 391];
}

View File

@@ -2,7 +2,6 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Charsets;
using CharStrings;
using Core;
@@ -23,7 +22,7 @@
this.privateDictionaryReader = privateDictionaryReader;
}
public CompactFontFormatFont Parse(CompactFontFormatData data, string name, ReadOnlySpan<byte> topDictionaryIndex, IReadOnlyList<string> stringIndex,
public CompactFontFormatFont Parse(CompactFontFormatData data, string name, ReadOnlySpan<byte> topDictionaryIndex, ReadOnlySpan<string> stringIndex,
CompactFontFormatIndex globalSubroutineIndex)
{
var individualData = new CompactFontFormatData(topDictionaryIndex.ToArray());
@@ -127,8 +126,10 @@
return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union<Type1CharStrings, Type2CharStrings>.Two(charStrings), fontEncoding);
}
private static ICompactFontFormatCharset ReadCharset(CompactFontFormatData data, CompactFontFormatTopLevelDictionary topDictionary,
CompactFontFormatIndex charStringIndex, IReadOnlyList<string> stringIndex)
private static ICompactFontFormatCharset ReadCharset(CompactFontFormatData data,
CompactFontFormatTopLevelDictionary topDictionary,
CompactFontFormatIndex charStringIndex,
ReadOnlySpan<string> stringIndex)
{
data.Seek(topDictionary.CharSetOffset);
@@ -180,13 +181,13 @@
}
}
private static string ReadString(int index, IReadOnlyList<string> stringIndex)
private static string ReadString(int index, ReadOnlySpan<string> stringIndex)
{
if (index >= 0 && index <= 390)
{
return CompactFontFormatStandardStrings.GetName(index);
}
if (index - 391 < stringIndex.Count)
if (index - 391 < stringIndex.Length)
{
return stringIndex[index - 391];
}
@@ -213,9 +214,10 @@
}
}
private CompactFontFormatCidFont ReadCidFont(CompactFontFormatData data, CompactFontFormatTopLevelDictionary topLevelDictionary,
private CompactFontFormatCidFont ReadCidFont(CompactFontFormatData data,
CompactFontFormatTopLevelDictionary topLevelDictionary,
int numberOfGlyphs,
IReadOnlyList<string> stringIndex,
ReadOnlySpan<string> stringIndex,
CompactFontFormatPrivateDictionary privateDictionary,
ICompactFontFormatCharset charset,
CompactFontFormatIndex globalSubroutines,

View File

@@ -10,9 +10,9 @@
{
private readonly List<Operand> operands = new List<Operand>();
public abstract TResult Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex);
public abstract TResult Read(CompactFontFormatData data, ReadOnlySpan<string> stringIndex);
protected TBuilder ReadDictionary(TBuilder builder, CompactFontFormatData data, IReadOnlyList<string> stringIndex)
protected TBuilder ReadDictionary(TBuilder builder, CompactFontFormatData data, ReadOnlySpan<string> stringIndex)
{
while (data.CanRead())
{
@@ -124,7 +124,7 @@
exponentMissing = false;
break;
case 0xa:
sb.Append(".");
sb.Append('.');
break;
case 0xb:
if (hasExponent)
@@ -132,7 +132,7 @@
// avoid duplicates
break;
}
sb.Append("E");
sb.Append('E');
exponentMissing = true;
hasExponent = true;
break;
@@ -149,7 +149,7 @@
case 0xd:
break;
case 0xe:
sb.Append("-");
sb.Append('-');
break;
case 0xf:
done = true;
@@ -165,7 +165,7 @@
// the exponent is missing, just append "0" to avoid an exception
// not sure if 0 is the correct value, but it seems to fit
// see PDFBOX-1522
sb.Append("0");
sb.Append('0');
}
if (sb.Length == 0)
@@ -176,9 +176,9 @@
return hasExponent ? double.Parse(sb.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture) : double.Parse(sb.ToString(), CultureInfo.InvariantCulture);
}
protected abstract void ApplyOperation(TBuilder builder, List<Operand> operands, OperandKey operandKey, IReadOnlyList<string> stringIndex);
protected abstract void ApplyOperation(TBuilder builder, List<Operand> operands, OperandKey operandKey, ReadOnlySpan<string> stringIndex);
protected static string GetString(List<Operand> operands, IReadOnlyList<string> stringIndex)
protected static string GetString(List<Operand> operands, ReadOnlySpan<string> stringIndex)
{
if (operands.Count == 0)
{
@@ -198,7 +198,7 @@
}
var stringIndexIndex = index - 391;
if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Count)
if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Length)
{
return stringIndex[stringIndexIndex];
}

View File

@@ -5,7 +5,7 @@
internal class CompactFontFormatPrivateDictionaryReader : CompactFontFormatDictionaryReader<CompactFontFormatPrivateDictionary, CompactFontFormatPrivateDictionary.Builder>
{
public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex)
public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, ReadOnlySpan<string> stringIndex)
{
var builder = new CompactFontFormatPrivateDictionary.Builder();
@@ -14,7 +14,7 @@
return builder.Build();
}
protected override void ApplyOperation(CompactFontFormatPrivateDictionary.Builder dictionary, List<Operand> operands, OperandKey operandKey, IReadOnlyList<string> stringIndex)
protected override void ApplyOperation(CompactFontFormatPrivateDictionary.Builder dictionary, List<Operand> operands, OperandKey operandKey, ReadOnlySpan<string> stringIndex)
{
switch (operandKey.Byte0)
{

View File

@@ -6,7 +6,7 @@
internal class CompactFontFormatTopLevelDictionaryReader : CompactFontFormatDictionaryReader<CompactFontFormatTopLevelDictionary, CompactFontFormatTopLevelDictionary>
{
public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex)
public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, ReadOnlySpan<string> stringIndex)
{
var dictionary = new CompactFontFormatTopLevelDictionary();
@@ -15,7 +15,7 @@
return dictionary;
}
protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List<Operand> operands, OperandKey key, IReadOnlyList<string> stringIndex)
protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List<Operand> operands, OperandKey key, ReadOnlySpan<string> stringIndex)
{
switch (key.Byte0)
{

View File

@@ -8,7 +8,7 @@
/// <summary>
/// Created by combining a base encoding with the differences.
/// </summary>
public class DifferenceBasedEncoding : Encoding
public sealed class DifferenceBasedEncoding : Encoding
{
/// <inheritdoc />
public override string EncodingName { get; } = "Difference Encoding";

View File

@@ -1,8 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Encodings
{
using Core;
internal class MacExpertEncoding : Encoding
internal sealed class MacExpertEncoding : Encoding
{
/// <summary>
/// Table of octal character codes and their corresponding names.

View File

@@ -1,12 +1,10 @@
namespace UglyToad.PdfPig.Fonts.Encodings
{
using Core;
/// <inheritdoc />
/// <summary>
/// Similar to the <see cref="T:UglyToad.PdfPig.Fonts.Encodings.MacRomanEncoding" /> with 15 additional entries.
/// </summary>
public class MacOsRomanEncoding : MacRomanEncoding
public sealed class MacOsRomanEncoding : MacRomanEncoding
{
private static readonly (int, string)[] EncodingTable =
{

View File

@@ -1,8 +1,5 @@
namespace UglyToad.PdfPig.Fonts.Encodings
{
using Core;
using System.Diagnostics;
{
/// <summary>
/// The Mac Roman encoding.
/// </summary>

View File

@@ -3,7 +3,7 @@
/// <summary>
/// The standard PDF encoding.
/// </summary>
public class StandardEncoding : Encoding
public sealed class StandardEncoding : Encoding
{
private static readonly (int, string)[] EncodingTable =
{

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Symbol encoding.
/// </summary>
public class SymbolEncoding : Encoding
public sealed class SymbolEncoding : Encoding
{
/// <summary>
/// EncodingTable for Symbol

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Windows ANSI encoding.
/// </summary>
public class WinAnsiEncoding : Encoding
public sealed class WinAnsiEncoding : Encoding
{
/// <summary>
/// The encoding table is taken from the Appendix of the specification.

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Zapf Dingbats encoding.
/// </summary>
public class ZapfDingbatsEncoding : Encoding
public sealed class ZapfDingbatsEncoding : Encoding
{
/// <summary>
/// EncodingTable for ZapfDingbats

View File

@@ -5,6 +5,7 @@
using System.Globalization;
using System.Text;
using Encodings;
using UglyToad.PdfPig.Util;
/// <summary>
/// A list which maps PostScript glyph names to unicode values.
@@ -117,7 +118,7 @@
var foundUnicode = true;
for (int chPos = 3; chPos + 4 <= nameLength; chPos += 4)
{
if (!int.TryParse(name.Substring(chPos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var codePoint))
if (!int.TryParse(name.AsSpanOrSubstring(chPos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var codePoint))
{
foundUnicode = false;
break;
@@ -138,10 +139,10 @@
unicode = uniStr.ToString();
}
else if (name.StartsWith("u") && name.Length == 5)
else if (name.StartsWith("u", StringComparison.Ordinal) && name.Length == 5)
{
// test for an alternate Unicode name representation uXXXX
var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
var codePoint = int.Parse(name.AsSpanOrSubstring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Util;
internal class GlyphListFactory
{
@@ -34,6 +35,8 @@
return ReadInternal(stream);
}
private static readonly char[] Semicolon = [';'];
private static GlyphList ReadInternal(Stream stream, int? defaultDictionaryCapacity = 0)
{
if (stream == null)
@@ -41,8 +44,8 @@
throw new ArgumentNullException(nameof(stream));
}
var result = defaultDictionaryCapacity.HasValue ? new Dictionary<string, string>(defaultDictionaryCapacity.Value)
: new Dictionary<string, string>();
var result = defaultDictionaryCapacity.HasValue ? new Dictionary<string, string>(defaultDictionaryCapacity.Value) : [];
using (var reader = new StreamReader(stream))
{
@@ -60,7 +63,7 @@
continue;
}
var parts = line.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var parts = line.Split(Semicolon, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
{
@@ -70,13 +73,16 @@
var key = parts[0];
var values = parts[1].Split(' ');
var valueReader = new StringSplitter(parts[1].AsSpan(), ' ');
var value = string.Empty;
foreach (var s in values)
{
var code = int.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
while (valueReader.TryRead(out var s))
{
#if NET6_0_OR_GREATER
var code = int.Parse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
#else
var code = int.Parse(s.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
#endif
value += char.ConvertFromUtf32(code);
}

View File

@@ -39,7 +39,7 @@
public static IGlyphDescription Empty(PdfRectangle bounds)
{
return new Glyph(true, new byte[0], new ushort[0], new GlyphPoint[0], bounds);
return new Glyph(true, [], [], [], bounds);
}
public IGlyphDescription DeepClone()

View File

@@ -87,7 +87,7 @@
return new CMapTable(tableVersionNumber, header, tables);
}
private class SubTableHeaderEntry
private readonly struct SubTableHeaderEntry
{
public TrueTypeCMapPlatform PlatformId { get; }

View File

@@ -65,7 +65,7 @@
var compositeIndicesToReplace = new List<(uint offset, ushort newIndex)>();
using (var stream = new MemoryStream())
using (var writer = new ArrayPoolBufferWriter<byte>())
{
for (var i = 0; i < glyphsToCopy.Count; i++)
{
@@ -102,7 +102,7 @@
}
// Record the glyph location.
glyphLocations.Add((uint)stream.Position);
glyphLocations.Add((uint)writer.WrittenCount);
var advanceWidth = advanceWidthTable.HorizontalMetrics[glyphsToCopyOriginalIndex[i]];
advanceWidths.Add(advanceWidth);
@@ -123,19 +123,19 @@
glyphBytes[offset] = (byte)(newIndex >> 8);
glyphBytes[offset + 1] = (byte)newIndex;
}
stream.Write(glyphBytes, 0, glyphBytes.Length);
writer.Write(glyphBytes);
// Each glyph description must start at a 4 byte boundary.
var remainder = glyphBytes.Length % 4;
var bytesToPad = remainder == 0 ? 0 : 4 - remainder;
for (var j = 0; j < bytesToPad; j++)
{
stream.WriteByte(0);
writer.Write(0);
}
}
var output = stream.ToArray();
var output = writer.WrittenSpan.ToArray();
glyphLocations.Add((uint)output.Length);
var offsets = glyphLocations.ToArray();

View File

@@ -169,7 +169,7 @@
switch (c1)
{
case 'n':
case 'r': stringBuffer.Append("\n"); break;
case 'r': stringBuffer.Append('\n'); break;
case 't': stringBuffer.Append('\t'); break;
case 'b': stringBuffer.Append('\b'); break;
case 'f': stringBuffer.Append('\f'); break;
@@ -180,14 +180,14 @@
// octal \ddd
if (char.IsDigit(c1))
{
var rawOctal = new string(new[] { c1, GetNext(), GetNext() });
var rawOctal = new string([c1, GetNext(), GetNext()]);
var code = Convert.ToInt32(rawOctal, 8);
stringBuffer.Append((char)code);
}
break;
case '\r':
case '\n':
stringBuffer.Append("\n");
stringBuffer.Append('\n');
break;
default:
stringBuffer.Append(c);

View File

@@ -0,0 +1,28 @@
using System;
namespace UglyToad.PdfPig.Util;
internal static class StringExtensions
{
#if NET
public static ReadOnlySpan<char> AsSpanOrSubstring(this string text, int start)
{
return text.AsSpan(start);
}
public static ReadOnlySpan<char> AsSpanOrSubstring(this string text, int start, int length)
{
return text.AsSpan(start, length);
}
#else
public static string AsSpanOrSubstring(this string text, int start)
{
return text.Substring(start);
}
public static string AsSpanOrSubstring(this string text, int start, int length)
{
return text.Substring(start, length);
}
#endif
}

View File

@@ -0,0 +1,70 @@
using System;
using System.IO;
namespace UglyToad.PdfPig.Util;
internal ref struct StringSplitter(ReadOnlySpan<char> text, char separator)
{
private readonly ReadOnlySpan<char> text = text;
private readonly char separator = separator;
private int position = 0;
public bool TryRead(out ReadOnlySpan<char> result)
{
if (IsEof)
{
result = default;
return false;
}
int separatorIndex = text.Slice(position).IndexOf(separator);
if (separatorIndex > -1)
{
result = text.Slice(position, separatorIndex);
position += separatorIndex + 1;
}
else
{
result = text.Slice(position);
position = text.Length;
}
return true;
}
public ReadOnlySpan<char> Read()
{
if (IsEof)
{
ThrowEof();
}
int start = position;
int separatorIndex = text.Slice(position).IndexOf(separator);
if (separatorIndex > -1)
{
position += separatorIndex + 1;
return text.Slice(start, separatorIndex);
}
else
{
position = text.Length;
return text.Slice(start);
}
}
public readonly bool IsEof => position == text.Length;
private static void ThrowEof()
{
throw new EndOfStreamException();
}
}

View File

@@ -5,6 +5,22 @@
public class EncodingsTests
{
[Fact]
public void Windows1252Encoding()
{
string path = IntegrationHelpers.GetDocumentPath("GHOSTSCRIPT-698363-0.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(1);
string actual = string.Concat(page.Letters.Select(l => l.Value));
// The expected string value is just here to make sure we have the same results across net versions.
// Feel free to correct/update it if chars are not actually correct.
string expected = "ҘҹЧѥЧКጹѝঐܮ̂ҥ҇ҁӃ࿋\u0c0dҀғҊ˺෨ཌආр෨ཌ̂ҘҹЧѥЧКጹѝঐܮ̂ҥ҇ҁӃ࿋\u0c0dҀғҊ˺෨ཌආр෨ཌ̂ݰႺࢥ༢࣭\u089aѽ̔ҫһҐ̔ݰႺࢥ༢࣭\u089aѽ̔ҫһҐ̔";
Assert.Equal(expected, actual);
}
}
[Fact]
public void Issue688()
{

View File

@@ -3,10 +3,11 @@
public class IntegrationDocumentTests
{
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
private static readonly HashSet<string> _documentsToIgnore = new HashSet<string>()
{
"issue_671.pdf"
};
private static readonly HashSet<string> _documentsToIgnore =
[
"issue_671.pdf",
"GHOSTSCRIPT-698363-0.pdf"
];
[Theory]
[MemberData(nameof(GetAllDocuments))]

View File

@@ -148,9 +148,9 @@
int code,
string unicode,
long currentOffset,
TransformationMatrix renderingMatrix,
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix,
in TransformationMatrix renderingMatrix,
in TransformationMatrix textMatrix,
in TransformationMatrix transformationMatrix,
CharacterBoundingBox characterBoundingBox)
{
_letters.Add(unicode);

View File

@@ -69,9 +69,9 @@
int code,
string unicode,
long currentOffset,
TransformationMatrix renderingMatrix,
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix,
in TransformationMatrix renderingMatrix,
in TransformationMatrix textMatrix,
in TransformationMatrix transformationMatrix,
CharacterBoundingBox characterBoundingBox)
{
if (textRenderingMode == TextRenderingMode.Neither)
@@ -98,9 +98,9 @@
IColor strokingColor,
IColor nonStrokingColor,
TextRenderingMode textRenderingMode,
TransformationMatrix renderingMatrix,
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix)
in TransformationMatrix renderingMatrix,
in TransformationMatrix textMatrix,
in TransformationMatrix transformationMatrix)
{
var transformMatrix = renderingMatrix.ToSKMatrix()
.PostConcat(textMatrix.ToSKMatrix())

View File

@@ -5,9 +5,18 @@
using Core;
using Tokens;
#if NET8_0_OR_GREATER
using System.Text.Unicode;
#endif
internal class NameTokenizer : ITokenizer
{
private static readonly ListPool<byte> ListPool = new ListPool<byte>(10);
static NameTokenizer()
{
#if NET6_0_OR_GREATER
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif
}
public bool ReadsNextByte { get; } = true;
@@ -20,11 +29,11 @@
return false;
}
var bytes = ListPool.Borrow();
using var bytes = new ArrayPoolBufferWriter<byte>();
bool escapeActive = false;
int postEscapeRead = 0;
var escapedChars = new char[2];
Span<char> escapedChars = stackalloc char[2];
while (inputBytes.MoveNext())
{
@@ -43,10 +52,12 @@
if (postEscapeRead == 2)
{
var hex = new string(escapedChars);
int high = escapedChars[0] <= '9' ? escapedChars[0] - '0' : char.ToUpper(escapedChars[0]) - 'A' + 10;
int low = escapedChars[1] <= '9' ? escapedChars[1] - '0' : char.ToUpper(escapedChars[1]) - 'A' + 10;
var characterToWrite = (byte)Convert.ToInt32(hex, 16);
bytes.Add(characterToWrite);
byte characterToWrite = (byte)(high * 16 + low);
bytes.Write(characterToWrite);
escapeActive = false;
postEscapeRead = 0;
@@ -54,11 +65,11 @@
}
else
{
bytes.Add((byte)'#');
bytes.Write((byte)'#');
if (postEscapeRead == 1)
{
bytes.Add((byte)escapedChars[0]);
bytes.Write((byte)escapedChars[0]);
}
if (ReadHelper.IsEndOfName(b))
@@ -75,7 +86,7 @@
continue;
}
bytes.Add(b);
bytes.Write(b);
escapeActive = false;
postEscapeRead = 0;
}
@@ -87,18 +98,22 @@
}
else
{
bytes.Add(b);
bytes.Write(b);
}
}
var byteArray = bytes.ToArray();
#if NET8_0_OR_GREATER
var byteArray = bytes.WrittenSpan;
bool isValidUtf8 = Utf8.IsValid(byteArray);
#else
var byteArray = bytes.WrittenSpan.ToArray();
bool isValidUtf8 = ReadHelper.IsValidUtf8(byteArray);
#endif
ListPool.Return(bytes);
var str = ReadHelper.IsValidUtf8(byteArray)
var str = isValidUtf8
? Encoding.UTF8.GetString(byteArray)
: Encoding.GetEncoding("windows-1252").GetString(byteArray);
token = NameToken.Create(str);
return true;

View File

@@ -176,7 +176,7 @@
CropBox cropBox,
UserSpaceUnit userSpaceUnit,
PageRotationDegrees rotation,
TransformationMatrix initialMatrix,
in TransformationMatrix initialMatrix,
ReadOnlyMemory<byte> contentBytes)
{
IReadOnlyList<IGraphicsStateOperation> operations;
@@ -315,7 +315,7 @@
/// <param name="transformationMatrix"></param>
/// <param name="mediaBox"></param>
/// <param name="cropBox"></param>
protected static void ApplyTransformNormalise(TransformationMatrix transformationMatrix, ref MediaBox mediaBox, ref CropBox cropBox)
protected static void ApplyTransformNormalise(in TransformationMatrix transformationMatrix, ref MediaBox mediaBox, ref CropBox cropBox)
{
if (transformationMatrix != TransformationMatrix.Identity)
{

View File

@@ -26,6 +26,9 @@
aes.Key = finalKey;
aes.IV = iv;
#if NET8_0_OR_GREATER
return aes.DecryptCbc(data.AsSpan(iv.Length), iv, PaddingMode.PKCS7);
#else
var buffer = new byte[256];
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
@@ -49,6 +52,7 @@
return output.ToArray();
}
}
#endif
}
}
}

View File

@@ -120,7 +120,7 @@
CropBox cropBox,
UserSpaceUnit userSpaceUnit,
PageRotationDegrees rotation,
TransformationMatrix initialMatrix,
in TransformationMatrix initialMatrix,
ParsingOptions parsingOptions)
{
this.PageNumber = pageNumber;
@@ -325,9 +325,9 @@
int code,
string unicode,
long currentOffset,
TransformationMatrix renderingMatrix,
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix,
in TransformationMatrix renderingMatrix,
in TransformationMatrix textMatrix,
in TransformationMatrix transformationMatrix,
CharacterBoundingBox characterBoundingBox);
/// <inheritdoc/>

View File

@@ -95,9 +95,9 @@ namespace UglyToad.PdfPig.Graphics
int code,
string unicode,
long currentOffset,
TransformationMatrix renderingMatrix,
TransformationMatrix textMatrix,
TransformationMatrix transformationMatrix,
in TransformationMatrix renderingMatrix,
in TransformationMatrix textMatrix,
in TransformationMatrix transformationMatrix,
CharacterBoundingBox characterBoundingBox)
{
var transformedGlyphBounds = PerformantRectangleTransformer

View File

@@ -25,7 +25,8 @@
/// </summary>
public ReadOnlyMemory<byte> Bytes { get; internal set; }
internal InlineImage CreateInlineImage(TransformationMatrix transformationMatrix,
internal InlineImage CreateInlineImage(
in TransformationMatrix transformationMatrix,
ILookupFilterProvider filterProvider,
IPdfTokenScanner tokenScanner,
RenderingIntent defaultRenderingIntent,

View File

@@ -1,10 +1,10 @@
namespace UglyToad.PdfPig.Graphics.Operations
{
using PdfPig.Core;
using System;
using System.Globalization;
using System.IO;
using UglyToad.PdfPig.Util;
using PdfPig.Core;
using Util;
internal static class OperationWriteHelper
{
@@ -23,9 +23,15 @@
public static void WriteHex(this Stream stream, ReadOnlySpan<byte> bytes)
{
var text = Hex.GetString(bytes);
Span<byte> hex = bytes.Length <= 64
? stackalloc byte[bytes.Length * 2]
: new byte[bytes.Length * 2];
stream.WriteText($"<{text}>");
Hex.GetUtf8Chars(bytes, hex);
stream.WriteByte((byte)'<');
stream.Write(hex);
stream.WriteByte((byte)'>');
}
public static void WriteWhiteSpace(this Stream stream)

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Transform the rectangle using the matrices.
/// </summary>
public static PdfRectangle Transform(TransformationMatrix first, TransformationMatrix second, TransformationMatrix third, PdfRectangle rectangle)
public static PdfRectangle Transform(in TransformationMatrix first, in TransformationMatrix second, in TransformationMatrix third, PdfRectangle rectangle)
{
var tl = rectangle.TopLeft;
var tr = rectangle.TopRight;

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Util
{
using System;
using System.Text;
/**
* Utility functions for hex encoding.
@@ -17,7 +16,18 @@
*
*/
private static readonly char[] HexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
public static void GetUtf8Chars(ReadOnlySpan<byte> bytes, Span<byte> utf8Chars)
{
int position = 0;
foreach (var b in bytes)
{
utf8Chars[position++] = (byte)HexChars[GetHighNibble(b)];
utf8Chars[position++] = (byte)HexChars[GetLowNibble(b)];
}
}
/// <summary>
/// Returns a hex string for the given byte array.
/// </summary>
@@ -26,14 +36,16 @@
#if NET6_0_OR_GREATER
return Convert.ToHexString(bytes);
#else
var stringBuilder = new StringBuilder(bytes.Length * 2);
var chars = new char[bytes.Length * 2];
int position = 0;
foreach (var b in bytes)
{
stringBuilder.Append(HexChars[GetHighNibble(b)]).Append(HexChars[GetLowNibble(b)]);
chars[position++] = HexChars[GetHighNibble(b)];
chars[position++] = HexChars[GetLowNibble(b)];
}
return stringBuilder.ToString();
return new string(chars);
#endif
}

View File

@@ -67,7 +67,7 @@
}
private static PatternColor CreateTilingPattern(StreamToken patternStream, DictionaryToken patternExtGState,
TransformationMatrix matrix, IPdfTokenScanner scanner)
in TransformationMatrix matrix, IPdfTokenScanner scanner)
{
if (!patternStream.StreamDictionary.TryGet<NumericToken>(NameToken.PaintType, scanner, out var paintTypeToken))
{
@@ -113,7 +113,7 @@
}
private static PatternColor CreateShadingPattern(DictionaryToken patternDictionary, DictionaryToken? patternExtGState,
TransformationMatrix matrix, IPdfTokenScanner scanner, IResourceStore resourceStore,
in TransformationMatrix matrix, IPdfTokenScanner scanner, IResourceStore resourceStore,
ILookupFilterProvider filterProvider)
{
IToken shadingToken = patternDictionary.Data[NameToken.Shading];

View File

@@ -989,7 +989,7 @@
return this;
}
private List<Letter> DrawLetters(NameToken? name, string text, IWritingFont font, TransformationMatrix fontMatrix, double fontSize, TransformationMatrix textMatrix)
private List<Letter> DrawLetters(NameToken? name, string text, IWritingFont font, in TransformationMatrix fontMatrix, double fontSize, TransformationMatrix textMatrix)
{
var horizontalScaling = 1;
var rise = 0;