mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 19:54:52 +08:00
merge from upstream
This commit is contained in:
1134
src/UglyToad.PdfPig.Tests/Fonts/Type1/AdobeUtopia.pfa
Normal file
1134
src/UglyToad.PdfPig.Tests/Fonts/Type1/AdobeUtopia.pfa
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/UglyToad.PdfPig.Tests/Fonts/Type1/Raleway-Black.pfb
Normal file
BIN
src/UglyToad.PdfPig.Tests/Fonts/Type1/Raleway-Black.pfb
Normal file
Binary file not shown.
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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>
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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()
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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()
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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');
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
||||||
|
@@ -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++;
|
||||||
|
@@ -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)
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user