merge from upstream

This commit is contained in:
modest-as
2018-04-13 00:32:12 +01:00
24 changed files with 1639 additions and 64 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,41 @@
namespace UglyToad.PdfPig.Tests.Fonts.Type1 namespace UglyToad.PdfPig.Tests.Fonts.Type1
{ {
using System;
using System.IO;
using System.Linq;
using System.Text;
using PdfPig.Fonts.Type1.Parser; using PdfPig.Fonts.Type1.Parser;
using PdfPig.IO;
using PdfPig.Util;
using Xunit; using Xunit;
public class Type1FontParserTests public class Type1FontParserTests
{ {
private readonly Type1FontParser parser = new Type1FontParser(); private readonly Type1FontParser parser = new Type1FontParser(new Type1EncryptedPortionParser());
[Fact] [Fact]
public void CanRead() public void CanReadHexEncryptedPortion()
{
var bytes = GetFileBytes("AdobeUtopia.pfa");
parser.Parse(new ByteArrayInputBytes(bytes),0, 0);
}
[Fact]
public void CanReadBinaryEncryptedPortionOfFullPfb()
{
// TODO: support reading in these pfb files
var bytes = GetFileBytes("Raleway-Black.pfb");
parser.Parse(new ByteArrayInputBytes(bytes), 0, 0);
}
[Fact]
public void CanReadAsciiPart()
{ {
var bytes = StringBytesTestConverter.Convert(Cmbx12, false); var bytes = StringBytesTestConverter.Convert(Cmbx12, false);
parser.Parse(bytes.Bytes); parser.Parse(bytes.Bytes, 0, 0);
} }
private const string Cmbx12 = @"%!PS-AdobeFont-1.1: CMBX12 1.0 private const string Cmbx12 = @"%!PS-AdobeFont-1.1: CMBX12 1.0
@@ -91,5 +114,20 @@ currentfile eexec
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
cleartomark"; cleartomark";
private static byte[] GetFileBytes(string name)
{
var manifestFiles = typeof(Type1FontParserTests).Assembly.GetManifestResourceNames();
var match = manifestFiles.Single(x => x.IndexOf(name, StringComparison.InvariantCultureIgnoreCase) >= 0);
using (var memoryStream = new MemoryStream())
using (var stream = typeof(Type1FontParserTests).Assembly.GetManifestResourceStream(match))
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
} }
} }

View File

@@ -0,0 +1,36 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System;
using System.IO;
using Xunit;
public class PigProductionHandbookTests
{
private static string GetFilename()
{
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
return Path.Combine(documentFolder, "Pig Production Handbook.pdf");
}
[Fact]
public void CanReadContent()
{
using (var document = PdfDocument.Open(GetFilename()))
{
var page = document.GetPage(1);
Assert.Contains("For the small holders at village level", page.Text);
}
}
[Fact]
public void HasCorrectNumberOfPages()
{
using (var document = PdfDocument.Open(GetFilename()))
{
Assert.Equal(86, document.NumberOfPages);
}
}
}
}

View File

@@ -0,0 +1,36 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System;
using System.IO;
using Xunit;
public class PigReproductionPowerpointTests
{
private static string GetFilename()
{
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
return Path.Combine(documentFolder, "Pig Reproduction Powerpoint.pdf");
}
[Fact]
public void CanReadContent()
{
using (var document = PdfDocument.Open(GetFilename()))
{
var page = document.GetPage(1);
Assert.Contains("Pigs per sow per year: 18 to 27", page.Text);
}
}
[Fact]
public void HasCorrectNumberOfPages()
{
using (var document = PdfDocument.Open(GetFilename()))
{
Assert.Equal(35, document.NumberOfPages);
}
}
}
}

View File

@@ -8,16 +8,21 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<None Remove="Fonts\TrueType\*" /> <None Remove="Fonts\TrueType\*.ttf" />
<None Remove="Fonts\Type1\*.pfa" />
<None Remove="Fonts\Type1\*.pfb" />
<None Remove="Integration\Documents\*" /> <None Remove="Integration\Documents\*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Fonts\TrueType\google-simple-doc.ttf"> <EmbeddedResource Include="Fonts\TrueType\*.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="Fonts\TrueType\Roboto-Regular.ttf"> <EmbeddedResource Include="Fonts\Type1\*.pfa">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="Fonts\Type1\*.pfb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource> </EmbeddedResource>
<Content Include="Integration\Documents\*"> <Content Include="Integration\Documents\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.CidFonts namespace UglyToad.PdfPig.Fonts.CidFonts
{ {
using Core; using Core;
using Geometry;
using Tokenization.Tokens; using Tokenization.Tokens;
/// <summary> /// <summary>
@@ -39,8 +40,8 @@
FontDescriptor Descriptor { get; } FontDescriptor Descriptor { get; }
decimal GetWidthFromFont(int characterCode);
decimal GetWidthFromDictionary(int cid); decimal GetWidthFromDictionary(int cid);
PdfRectangle GetBoundingBox(int characterCode);
} }
} }

View File

@@ -1,10 +1,12 @@
namespace UglyToad.PdfPig.Fonts.CidFonts namespace UglyToad.PdfPig.Fonts.CidFonts
{ {
using Geometry;
/// <summary> /// <summary>
/// Represents either an Adobe Type 1 or TrueType font program for a CIDFont. /// Represents either an Adobe Type 1 or TrueType font program for a CIDFont.
/// </summary> /// </summary>
internal interface ICidFontProgram internal interface ICidFontProgram
{ {
bool TryGetBoundingBox(int characterCode, out PdfRectangle boundingBox);
} }
} }

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.CidFonts namespace UglyToad.PdfPig.Fonts.CidFonts
{ {
using Core; using Core;
using Geometry;
using Tokenization.Tokens; using Tokenization.Tokens;
/// <inheritdoc/> /// <inheritdoc/>
@@ -32,5 +33,10 @@
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public PdfRectangle GetBoundingBox(int characterCode)
{
throw new System.NotImplementedException();
}
} }
} }

View File

@@ -2,6 +2,7 @@
{ {
using System.Collections.Generic; using System.Collections.Generic;
using Core; using Core;
using Geometry;
using Tokenization.Tokens; using Tokenization.Tokens;
/// <inheritdoc /> /// <inheritdoc />
@@ -57,5 +58,20 @@
return Descriptor.MissingWidth; return Descriptor.MissingWidth;
} }
public PdfRectangle GetBoundingBox(int characterCode)
{
if (fontProgram == null)
{
return Descriptor.BoundingBox;
}
if (fontProgram.TryGetBoundingBox(characterCode, out var result))
{
return result;
}
return Descriptor.BoundingBox;
}
} }
} }

View File

@@ -76,13 +76,18 @@
return fontMatrix.Transform(GetBoundingBoxInGlyphSpace(characterCode)); return fontMatrix.Transform(GetBoundingBoxInGlyphSpace(characterCode));
} }
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode) public decimal GetWidth(int characterCode)
{ {
var cid = CMap.ConvertToCid(characterCode); var cid = CMap.ConvertToCid(characterCode);
var fromFont = CidFont.GetWidthFromDictionary(cid); var fromFont = CidFont.GetWidthFromDictionary(cid);
return new PdfRectangle(0, 0, fromFont, 0); return fromFont;
}
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
{
return CidFont.GetBoundingBox(characterCode);
} }
public TransformationMatrix GetFontMatrix() public TransformationMatrix GetFontMatrix()

View File

@@ -12,6 +12,7 @@
using Tokenization.Tokens; using Tokenization.Tokens;
using Type1; using Type1;
using Type1.Parser; using Type1.Parser;
using Util;
internal class Type1FontHandler : IFontHandler internal class Type1FontHandler : IFontHandler
{ {
@@ -102,16 +103,17 @@
try try
{ {
var stream = pdfScanner.Get(descriptor.FontFile.ObjectKey.Data).Data as StreamToken; if (!(pdfScanner.Get(descriptor.FontFile.ObjectKey.Data).Data is StreamToken stream))
if (stream == null)
{ {
return null; return null;
} }
var length1 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length1, pdfScanner);
var length2 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length2, pdfScanner);
var bytes = stream.Decode(filterProvider); var bytes = stream.Decode(filterProvider);
var font = type1FontParser.Parse(new ByteArrayInputBytes(bytes)); var font = type1FontParser.Parse(new ByteArrayInputBytes(bytes), length1.Int, length2.Int);
return font; return font;
} }

View File

@@ -85,19 +85,17 @@
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode) private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
{ {
if (font?.CMapTable == null) if (font == null)
{ {
return descriptor.BoundingBox; return descriptor.BoundingBox;
} }
if (!font.CMapTable.TryGetGlyphIndex(characterCode, out var index)) if (font.TryGetBoundingBox(characterCode, out var bounds))
{ {
return descriptor.BoundingBox; return bounds;
} }
var glyph = font.GlyphTable.Glyphs[index]; return descriptor.BoundingBox;
return glyph?.GlyphBounds ?? descriptor.BoundingBox;
} }
public TransformationMatrix GetFontMatrix() public TransformationMatrix GetFontMatrix()

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using CidFonts; using CidFonts;
using Geometry;
using Parser; using Parser;
using Tables; using Tables;
@@ -31,5 +32,31 @@
CMapTable = tableRegister.CMapTable; CMapTable = tableRegister.CMapTable;
GlyphTable = tableRegister.GlyphDataTable; GlyphTable = tableRegister.GlyphDataTable;
} }
public bool TryGetBoundingBox(int characterCode, out PdfRectangle boundingBox)
{
boundingBox = default(PdfRectangle);
if (CMapTable == null)
{
return false;
}
if (!CMapTable.TryGetGlyphIndex(characterCode, out var index))
{
return false;
}
var glyph = GlyphTable.Glyphs[index];
if (glyph?.GlyphBounds == null)
{
return false;
}
boundingBox = glyph.GlyphBounds;
return true;
}
} }
} }

View File

@@ -0,0 +1,123 @@
namespace UglyToad.PdfPig.Fonts.Type1.Parser
{
using System.Collections.Generic;
using System.Linq;
using PdfPig.Parser.Parts;
using Tokenization.Tokens;
using Util;
internal class Type1EncryptedPortionParser
{
private const ushort EexecEncryptionKey = 55665;
private const int EexecRandomBytes = 4;
public void Parse(IReadOnlyList<byte> bytes)
{
if (!IsBinary(bytes))
{
bytes = ConvertHexToBinary(bytes);
}
var decrypted = Decrypt(bytes, EexecEncryptionKey, EexecRandomBytes);
var str = OtherEncodings.BytesAsLatin1String(decrypted.ToArray());
}
/// <summary>
/// To distinguish between binary and hex the first 4 bytes (of the ciphertext) for hex must
/// obey these restrictions:
/// The first byte must not be whitespace.
/// One of the first four ciphertext bytes must not be an ASCII hex character.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
private static bool IsBinary(IReadOnlyList<byte> bytes)
{
if (bytes.Count < 4)
{
return true;
}
if (ReadHelper.IsWhitespace(bytes[0]))
{
return true;
}
for (var i = 1; i < 4; i++)
{
var b = bytes[i];
if (!ReadHelper.IsHex(b))
{
return true;
}
}
return false;
}
private static IReadOnlyList<byte> ConvertHexToBinary(IReadOnlyList<byte> bytes)
{
var result = new List<byte>(bytes.Count / 2);
var last = '\0';
var offset = 0;
for (var i = 0; i < bytes.Count; i++)
{
var c = (char)bytes[i];
if (!ReadHelper.IsHex(c))
{
// TODO: do I need to assert this must be whitespace?
continue;
}
if (offset == 1)
{
result.Add(HexToken.Convert(last, c));
offset = 0;
}
else
{
offset++;
}
last = c;
}
return result;
}
private static IReadOnlyList<byte> Decrypt(IReadOnlyList<byte> bytes, int key, int randomBytes)
{
if (randomBytes == -1)
{
return bytes;
}
if (randomBytes > bytes.Count || bytes.Count == 0)
{
return new byte[0];
}
const int c1 = 52845;
const int c2 = 22719;
var plainBytes = new byte[bytes.Count - randomBytes];
for (var i = 0; i < bytes.Count; i++)
{
var cipher = bytes[i] & 0xFF;
var plain = cipher ^ key >> 8;
if (i >= randomBytes)
{
plainBytes[i - randomBytes] = (byte)plain;
}
key = (cipher + key) * c1 + c2 & 0xffff;
}
return plainBytes;
}
}
}

View File

@@ -11,8 +11,39 @@
internal class Type1FontParser internal class Type1FontParser
{ {
public Type1Font Parse(IInputBytes inputBytes) private const string ClearToMark = "cleartomark";
private const int PfbFileIndicator = 0x80;
private readonly Type1EncryptedPortionParser encryptedPortionParser;
public Type1FontParser(Type1EncryptedPortionParser encryptedPortionParser)
{ {
this.encryptedPortionParser = encryptedPortionParser;
}
/// <summary>
/// Parses an embedded Adobe Type 1 font file.
/// </summary>
/// <param name="inputBytes">The bytes of the font program.</param>
/// <param name="length1">The length in bytes of the clear text portion of the font program.</param>
/// <param name="length2">The length in bytes of the encrypted portion of the font program.</param>
/// <returns>The parsed type 1 font.</returns>
public 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<byte> eexecPortion = new byte[0];
if (isEntirePfbFile)
{
var (ascii, binary) = ReadPfbHeader(inputBytes);
eexecPortion = binary;
inputBytes = new ByteArrayInputBytes(ascii);
}
var scanner = new CoreTokenScanner(inputBytes); var scanner = new CoreTokenScanner(inputBytes);
if (!scanner.TryReadToken(out CommentToken comment) || !comment.Data.StartsWith("!")) if (!scanner.TryReadToken(out CommentToken comment) || !comment.Data.StartsWith("!"))
@@ -48,17 +79,62 @@
try try
{ {
var tempEexecPortion = new List<byte>();
var tokenSet = new PreviousTokenSet(); var tokenSet = new PreviousTokenSet();
tokenSet.Add(scanner.CurrentToken); tokenSet.Add(scanner.CurrentToken);
while (scanner.MoveNext()) while (scanner.MoveNext())
{ {
if (scanner.CurrentToken is OperatorToken operatorToken) if (scanner.CurrentToken is OperatorToken operatorToken)
{ {
HandleOperator(operatorToken, inputBytes, scanner, tokenSet, dictionaries); if (Equals(scanner.CurrentToken, OperatorToken.Eexec))
{
int offset = 0;
while (inputBytes.MoveNext())
{
if (inputBytes.CurrentByte == (byte)ClearToMark[offset])
{
offset++;
}
else
{
if (offset > 0)
{
for (int i = 0; i < offset; i++)
{
tempEexecPortion.Add((byte)ClearToMark[i]);
}
}
offset = 0;
}
if (offset == ClearToMark.Length)
{
break;
}
if (offset > 0)
{
continue;
}
tempEexecPortion.Add(inputBytes.CurrentByte);
}
}
else
{
HandleOperator(operatorToken, scanner, tokenSet, dictionaries);
}
} }
tokenSet.Add(scanner.CurrentToken); tokenSet.Add(scanner.CurrentToken);
} }
if (!isEntirePfbFile)
{
eexecPortion = tempEexecPortion;
}
} }
finally finally
{ {
@@ -70,10 +146,79 @@
var matrix = GetFontMatrix(dictionaries); var matrix = GetFontMatrix(dictionaries);
var boundingBox = GetBoundingBox(dictionaries); var boundingBox = GetBoundingBox(dictionaries);
encryptedPortionParser.Parse(eexecPortion);
return new Type1Font(name, encoding, matrix, boundingBox); return new Type1Font(name, encoding, matrix, boundingBox);
} }
private void HandleOperator(OperatorToken token, IInputBytes bytes, ISeekableTokenScanner scanner, PreviousTokenSet set, List<DictionaryToken> dictionaries) /// <summary>
/// Where an entire PFB file has been embedded in the PDF we read the header first.
/// </summary>
private static (byte[] ascii, byte[] binary) ReadPfbHeader(IInputBytes bytes)
{
/*
* The header is a 6 byte sequence. The first byte is 0x80 followed by 0x01 for the ASCII record indicator.
* The following 4 bytes determine the size/length of the ASCII part of the PFB file.
* After the ASCII part another 6 byte sequence is present, this time 0x80 0x02 for the Binary part length.
* A 3rd sequence is present at the end re-stating the ASCII length but this is surplus to requirements.
*/
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
int ReadSize(byte recordType)
{
bytes.MoveNext();
if (bytes.CurrentByte != PfbFileIndicator)
{
throw new InvalidOperationException($"File does not start with 0x80, which indicates a full PFB file. Instead got: {bytes.CurrentByte}");
}
bytes.MoveNext();
if (bytes.CurrentByte != recordType)
{
throw new InvalidOperationException($"Encountered unexpected header type in the PFB file: {bytes.CurrentByte}");
}
bytes.MoveNext();
int size = bytes.CurrentByte;
bytes.MoveNext();
size += bytes.CurrentByte << 8;
bytes.MoveNext();
size += bytes.CurrentByte << 16;
bytes.MoveNext();
size += bytes.CurrentByte << 24;
return size;
}
var asciiSize = ReadSize(0x01);
var asciiPart = new byte[asciiSize];
int i = 0;
while (i < asciiSize)
{
bytes.MoveNext();
asciiPart[i] = bytes.CurrentByte;
i++;
}
var binarySize = ReadSize(0x02);
var binaryPart = new byte[binarySize];
i = 0;
while (i < binarySize)
{
bytes.MoveNext();
binaryPart[i] = bytes.CurrentByte;
i++;
}
return (asciiPart, binaryPart);
}
private static void HandleOperator(OperatorToken token, ISeekableTokenScanner scanner, PreviousTokenSet set, List<DictionaryToken> dictionaries)
{ {
switch (token.Data) switch (token.Data)
{ {
@@ -83,30 +228,11 @@
dictionaries.Add(dictionary); dictionaries.Add(dictionary);
break; break;
case "currentfile":
if (!scanner.MoveNext() || scanner.CurrentToken != OperatorToken.Eexec)
{
return;
}
// For now we will not read this stuff.
SkipEncryptedContent(bytes);
break;
default: default:
return; return;
} }
} }
private void SkipEncryptedContent(IInputBytes bytes)
{
bytes.Seek(bytes.Length - 1);
while (bytes.MoveNext())
{
// skip to end.
}
}
private static DictionaryToken ReadDictionary(int keys, ISeekableTokenScanner scanner) private static DictionaryToken ReadDictionary(int keys, ISeekableTokenScanner scanner)
{ {
IToken previousToken = null; IToken previousToken = null;
@@ -234,8 +360,8 @@
{ {
for (var i = 0; i < encodingArray.Data.Count; i += 2) for (var i = 0; i < encodingArray.Data.Count; i += 2)
{ {
var code = (NumericToken) encodingArray.Data[i]; var code = (NumericToken)encodingArray.Data[i];
var name = (NameToken) encodingArray.Data[i + 1]; var name = (NameToken)encodingArray.Data[i + 1];
result[code.Int] = name.Data; result[code.Int] = name.Data;
} }
@@ -266,10 +392,10 @@
{ {
if (dictionary.TryGet(NameToken.FontBbox, out var token) && token is ArrayToken array && array.Data.Count == 4) if (dictionary.TryGet(NameToken.FontBbox, out var token) && token is ArrayToken array && array.Data.Count == 4)
{ {
var x1 = (NumericToken) array.Data[0]; var x1 = (NumericToken)array.Data[0];
var y1 = (NumericToken) array.Data[1]; var y1 = (NumericToken)array.Data[1];
var x2 = (NumericToken) array.Data[2]; var x2 = (NumericToken)array.Data[2];
var y2 = (NumericToken) array.Data[3]; var y2 = (NumericToken)array.Data[3];
return new PdfRectangle(x1.Data, y1.Data, x2.Data, y2.Data); return new PdfRectangle(x1.Data, y1.Data, x2.Data, y2.Data);
} }

View File

@@ -1,11 +1,13 @@
namespace UglyToad.PdfPig.IO namespace UglyToad.PdfPig.IO
{ {
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
internal class ByteArrayInputBytes : IInputBytes internal class ByteArrayInputBytes : IInputBytes
{ {
private readonly IReadOnlyList<byte> bytes; private readonly IReadOnlyList<byte> bytes;
[DebuggerStepThrough]
public ByteArrayInputBytes(IReadOnlyList<byte> bytes) public ByteArrayInputBytes(IReadOnlyList<byte> bytes)
{ {
this.bytes = bytes; this.bytes = bytes;

View File

@@ -251,7 +251,8 @@
return ' ' == c; return ' ' == c;
} }
public static bool IsHexDigit(char ch) public static bool IsHex(byte b) => IsHex((char) b);
public static bool IsHex(char ch)
{ {
return char.IsDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); return char.IsDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
} }

View File

@@ -104,7 +104,7 @@
cMapCache, cMapCache,
filterProvider, pdfScanner), filterProvider, pdfScanner),
new TrueTypeFontHandler(log, pdfScanner, filterProvider, cMapCache, fontDescriptorFactory, trueTypeFontParser, encodingReader), new TrueTypeFontHandler(log, pdfScanner, filterProvider, cMapCache, fontDescriptorFactory, trueTypeFontParser, encodingReader),
new Type1FontHandler(pdfScanner, cMapCache, filterProvider, fontDescriptorFactory, encodingReader, new Type1FontParser()), new Type1FontHandler(pdfScanner, cMapCache, filterProvider, fontDescriptorFactory, encodingReader, new Type1FontParser(new Type1EncryptedPortionParser())),
new Type3FontHandler(pdfScanner, cMapCache, filterProvider, encodingReader)); new Type3FontHandler(pdfScanner, cMapCache, filterProvider, encodingReader));
var resourceContainer = new ResourceContainer(pdfScanner, fontFactory); var resourceContainer = new ResourceContainer(pdfScanner, fontFactory);

View File

@@ -36,7 +36,7 @@
} }
else if (escapeActive) else if (escapeActive)
{ {
if (ReadHelper.IsHexDigit((char)b)) if (ReadHelper.IsHex((char)b))
{ {
escapedChars[postEscapeRead] = (char)b; escapedChars[postEscapeRead] = (char)b;
postEscapeRead++; postEscapeRead++;

View File

@@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Parser.Parts;
using Scanner;
using Util.JetBrains.Annotations; using Util.JetBrains.Annotations;
internal class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>> internal class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>>
@@ -40,6 +42,21 @@
Data = data; Data = data;
} }
public T Get<T>(NameToken name, IPdfTokenScanner scanner) where T : IToken
{
if (!TryGet(name, out var token) || !(token is T typedToken))
{
if (!(token is IndirectReferenceToken indirectReference))
{
throw new InvalidOperationException($"Dictionary does not contain token with name {name} of type {typeof(T).Name}.");
}
typedToken = DirectObjectFinder.Get<T>(indirectReference, scanner);
}
return typedToken;
}
public bool TryGet(NameToken name, out IToken token) public bool TryGet(NameToken name, out IToken token)
{ {
if (name == null) if (name == null)

View File

@@ -32,14 +32,6 @@ namespace UglyToad.PdfPig.Tokenization.Tokens
{'f', 0x0F } {'f', 0x0F }
}; };
private static byte Convert(char high, char low)
{
var highByte = HexMap[high];
var lowByte = HexMap[low];
return (byte)(highByte << 4 | lowByte);
}
public string Data { get; } public string Data { get; }
public IReadOnlyList<byte> Bytes { get; } public IReadOnlyList<byte> Bytes { get; }
@@ -75,6 +67,14 @@ namespace UglyToad.PdfPig.Tokenization.Tokens
Data = builder.ToString(); Data = builder.ToString();
} }
public static byte Convert(char high, char low)
{
var highByte = HexMap[high];
var lowByte = HexMap[low];
return (byte)(highByte << 4 | lowByte);
}
public static int ConvertHexBytesToInt(HexToken token) public static int ConvertHexBytesToInt(HexToken token)
{ {
var bytes = token.Bytes; var bytes = token.Bytes;