mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-14 19:05:01 +08:00
#21 change dictionarytoken to use explicit key type, finish os/2 table for truetype, first file creation using embedded truetype font
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
{
|
||||
private readonly Ascii85Filter filter = new Ascii85Filter();
|
||||
|
||||
private readonly DictionaryToken dictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
private readonly DictionaryToken dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
|
||||
[Fact]
|
||||
public void DecodesWikipediaExample()
|
||||
|
@@ -9,7 +9,7 @@
|
||||
|
||||
public class AsciiHexDecodeFilterTests
|
||||
{
|
||||
private readonly DictionaryToken dictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
private readonly DictionaryToken dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
|
||||
[Fact]
|
||||
public void DecodesEncodedTextProperly()
|
||||
|
@@ -21,7 +21,7 @@
|
||||
[Fact]
|
||||
public void NegativeIndex_Throws()
|
||||
{
|
||||
Action action = () => resolver.GetFilterParameters(new DictionaryToken(new Dictionary<IToken, IToken>()), -1);
|
||||
Action action = () => resolver.GetFilterParameters(new DictionaryToken(new Dictionary<NameToken, IToken>()), -1);
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(action);
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
[Fact]
|
||||
public void EmptyDictionary_ReturnsEmptyDictionary()
|
||||
{
|
||||
var result = resolver.GetFilterParameters(new DictionaryToken(new Dictionary<IToken, IToken>()), 0);
|
||||
var result = resolver.GetFilterParameters(new DictionaryToken(new Dictionary<NameToken, IToken>()), 0);
|
||||
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@
|
||||
1, 10, 19
|
||||
};
|
||||
|
||||
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<IToken, IToken>()), 1);
|
||||
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), 1);
|
||||
|
||||
var expectedResult = new byte[]
|
||||
{
|
||||
@@ -55,7 +55,7 @@
|
||||
90, 6, 7
|
||||
};
|
||||
|
||||
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<IToken, IToken>()), 0);
|
||||
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), 0);
|
||||
|
||||
var expectedResult = new byte[]
|
||||
{
|
||||
|
@@ -88,13 +88,13 @@
|
||||
|
||||
var streamBytes = BytesBetween(streamPosition.end + 1, endStreamPosition.start - 1, bytes);
|
||||
|
||||
var paramsDict = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var paramsDict = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Predictor, new NumericToken(12) },
|
||||
{ NameToken.Columns, new NumericToken(4) }
|
||||
});
|
||||
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{NameToken.Filter, NameToken.FlateDecode},
|
||||
{NameToken.DecodeParms, paramsDict}
|
||||
@@ -162,7 +162,7 @@
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static string GetNthFilename(int n = 0)
|
||||
private static string GetNthFilename()
|
||||
{
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
|
||||
|
@@ -20,7 +20,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void EmptyDictionaryValid()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
|
||||
Assert.Empty(dictionary.Data);
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void TryGetByName_EmptyDictionary()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
|
||||
var result = dictionary.TryGet(NameToken.ActualText, out var token);
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void TryGetByName_NullName_Throws()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
|
||||
Action action = () => dictionary.TryGet(null, out var _);
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void TryGetByName_NonEmptyDictionaryNotContainingKey()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Create("Registry"), new StringToken("None") }
|
||||
});
|
||||
@@ -63,7 +63,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void TryGetByName_ContainingKey()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Create("Fish"), new NumericToken(420) },
|
||||
{ NameToken.Create("Registry"), new StringToken("None") }
|
||||
@@ -74,22 +74,11 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
Assert.True(result);
|
||||
Assert.Equal("None", Assert.IsType<StringToken>(token).Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateWithNonNameTokensThrows()
|
||||
{
|
||||
Action action = () => new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
{
|
||||
{ new NumericToken(7), NameToken.N }
|
||||
});
|
||||
|
||||
Assert.Throws<PdfDocumentFormatException>(action);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void GetWithObjectNotOfTypeOrReferenceThrows()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Count, new StringToken("twelve") }
|
||||
});
|
||||
@@ -102,7 +91,7 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
[Fact]
|
||||
public void WithCorrectlyAddsKey()
|
||||
{
|
||||
var dictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var dictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Count, new StringToken("12") }
|
||||
});
|
||||
|
@@ -101,6 +101,18 @@
|
||||
var b = builder.Build();
|
||||
|
||||
Assert.NotEmpty(b);
|
||||
|
||||
using (var document = PdfDocument.Open(b))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
|
||||
Assert.Equal("Hello World!", page1.Text);
|
||||
|
||||
var h = page1.Letters[0];
|
||||
|
||||
Assert.Equal("H", h.Value);
|
||||
Assert.Equal("Andada-Regular", h.FontName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ContentStream;
|
||||
using Cos;
|
||||
using Logging;
|
||||
using Tokens;
|
||||
|
||||
@@ -32,7 +30,7 @@
|
||||
public CrossReferenceTable Build(long firstCrossReferenceOffset, ILog log)
|
||||
{
|
||||
CrossReferenceType type = CrossReferenceType.Table;
|
||||
DictionaryToken trailerDictionary = new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
DictionaryToken trailerDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
Dictionary<IndirectReference, long> objectOffsets = new Dictionary<IndirectReference, long>();
|
||||
|
||||
List<long> xrefSeqBytePos = new List<long>();
|
||||
|
@@ -55,7 +55,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
return new DictionaryToken(new Dictionary<IToken, IToken>());
|
||||
return new DictionaryToken(new Dictionary<NameToken, IToken>());
|
||||
}
|
||||
|
||||
private static IToken GetDictionaryObject(DictionaryToken dictionary, NameToken first, NameToken second)
|
||||
|
@@ -26,5 +26,10 @@
|
||||
Offset = offset;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({PlatformId}, {NameId}) - {Value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
151
src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs
Normal file
151
src/UglyToad.PdfPig/Fonts/TrueType/Parser/NameTableParser.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System.Text;
|
||||
using Names;
|
||||
using Tables;
|
||||
using Util;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
internal class NameTableParser : ITrueTypeTableParser<NameTable>
|
||||
{
|
||||
public NameTable Parse(TrueTypeHeaderTable header, TrueTypeDataBytes data, TableRegister.Builder register)
|
||||
{
|
||||
data.Seek(header.Offset);
|
||||
// ReSharper disable once UnusedVariable
|
||||
var format = data.ReadUnsignedShort();
|
||||
var count = data.ReadUnsignedShort();
|
||||
var stringOffset = data.ReadUnsignedShort();
|
||||
|
||||
var names = new NameRecordBuilder[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
names[i] = NameRecordBuilder.Read(data);
|
||||
}
|
||||
|
||||
var strings = new TrueTypeNameRecord[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var nameRecord = names[i];
|
||||
|
||||
var encoding = OtherEncodings.Iso88591;
|
||||
|
||||
switch (nameRecord.PlatformId)
|
||||
{
|
||||
case TrueTypePlatformIdentifier.Windows:
|
||||
{
|
||||
var platformEncoding = (TrueTypeWindowsEncodingIdentifier)nameRecord.PlatformEncodingId;
|
||||
|
||||
if (platformEncoding == TrueTypeWindowsEncodingIdentifier.Symbol
|
||||
|| platformEncoding == TrueTypeWindowsEncodingIdentifier.UnicodeBmp)
|
||||
{
|
||||
encoding = Encoding.BigEndianUnicode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TrueTypePlatformIdentifier.Unicode:
|
||||
{
|
||||
encoding = Encoding.BigEndianUnicode;
|
||||
break;
|
||||
}
|
||||
case TrueTypePlatformIdentifier.Iso:
|
||||
{
|
||||
switch (nameRecord.PlatformEncodingId)
|
||||
{
|
||||
case 0:
|
||||
encoding = Encoding.GetEncoding("US-ASCII");
|
||||
break;
|
||||
case 1:
|
||||
encoding = Encoding.GetEncoding("ISO-10646-UCS-2");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var position = header.Offset + stringOffset + nameRecord.Offset;
|
||||
|
||||
data.Seek(position);
|
||||
|
||||
var str = data.ReadString(nameRecord.Length, encoding);
|
||||
|
||||
strings[i] = nameRecord.ToNameRecord(str);
|
||||
}
|
||||
|
||||
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
private static string GetName(int nameId, TrueTypeNameRecord[] names)
|
||||
{
|
||||
const int windowsEnUs = 409;
|
||||
string windows = null;
|
||||
string any = null;
|
||||
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = names[i];
|
||||
|
||||
if (name.NameId != nameId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.PlatformId == TrueTypePlatformIdentifier.Windows && name.LanguageId == windowsEnUs)
|
||||
{
|
||||
return name.Value;
|
||||
}
|
||||
|
||||
if (name.PlatformId == TrueTypePlatformIdentifier.Windows)
|
||||
{
|
||||
windows = name.Value;
|
||||
}
|
||||
|
||||
any = name.Value;
|
||||
}
|
||||
|
||||
return windows ?? any;
|
||||
}
|
||||
|
||||
private struct NameRecordBuilder
|
||||
{
|
||||
public TrueTypePlatformIdentifier PlatformId { get; }
|
||||
|
||||
public int PlatformEncodingId { get; }
|
||||
|
||||
private int LanguageId { get; }
|
||||
|
||||
private int NameId { get; }
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public int Offset { get; }
|
||||
|
||||
private NameRecordBuilder(int platformId, int platformEncodingId, int languageId, int nameId, int length, int offset)
|
||||
{
|
||||
PlatformId = (TrueTypePlatformIdentifier)platformId;
|
||||
PlatformEncodingId = platformEncodingId;
|
||||
LanguageId = languageId;
|
||||
NameId = nameId;
|
||||
Length = length;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public TrueTypeNameRecord ToNameRecord(string s)
|
||||
{
|
||||
return new TrueTypeNameRecord(PlatformId, PlatformEncodingId,
|
||||
LanguageId, NameId, Length, Offset, s);
|
||||
}
|
||||
|
||||
public static NameRecordBuilder Read(TrueTypeDataBytes data)
|
||||
{
|
||||
return new NameRecordBuilder(data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -73,14 +73,60 @@
|
||||
|
||||
if (version == 0)
|
||||
{
|
||||
return null;
|
||||
return new Os2RevisedVersion0Table(header, version, xAvgCharWidth,
|
||||
weightClass, widthClass, typeFlags, ySubscriptXSize,
|
||||
ySubscriptYSize,
|
||||
ySubscriptXOffset,
|
||||
ySubscriptYOffset,
|
||||
ySuperscriptXSize,
|
||||
ySuperscriptYSize,
|
||||
ySuperscriptXOffset,
|
||||
ySuperscriptYOffset,
|
||||
yStrikeoutSize,
|
||||
yStrikeoutPosition,
|
||||
familyClass,
|
||||
panose,
|
||||
unicodeCharRange,
|
||||
Encoding.Unicode.GetString(vendorId),
|
||||
selectionFlags,
|
||||
firstCharacterIndex,
|
||||
lastCharacterIndex,
|
||||
sTypoAscender,
|
||||
sTypoDescender,
|
||||
sTypoLineGap,
|
||||
usWinAscent,
|
||||
usWinDescent);
|
||||
}
|
||||
|
||||
var ulCodePageRange1 = data.ReadUnsignedInt();
|
||||
var ulCodePageRange2 = data.ReadUnsignedInt();
|
||||
var ulCodePageRange1 = (uint)data.ReadUnsignedInt();
|
||||
var ulCodePageRange2 = (uint)data.ReadUnsignedInt();
|
||||
if (version == 1)
|
||||
{
|
||||
return null;
|
||||
return new Os2Version1Table(header, version, xAvgCharWidth,
|
||||
weightClass, widthClass, typeFlags, ySubscriptXSize,
|
||||
ySubscriptYSize,
|
||||
ySubscriptXOffset,
|
||||
ySubscriptYOffset,
|
||||
ySuperscriptXSize,
|
||||
ySuperscriptYSize,
|
||||
ySuperscriptXOffset,
|
||||
ySuperscriptYOffset,
|
||||
yStrikeoutSize,
|
||||
yStrikeoutPosition,
|
||||
familyClass,
|
||||
panose,
|
||||
unicodeCharRange,
|
||||
Encoding.Unicode.GetString(vendorId),
|
||||
selectionFlags,
|
||||
firstCharacterIndex,
|
||||
lastCharacterIndex,
|
||||
sTypoAscender,
|
||||
sTypoDescender,
|
||||
sTypoLineGap,
|
||||
usWinAscent,
|
||||
usWinDescent,
|
||||
ulCodePageRange1,
|
||||
ulCodePageRange2);
|
||||
}
|
||||
|
||||
var sxHeight = data.ReadSignedShort();
|
||||
@@ -91,14 +137,73 @@
|
||||
|
||||
if (version < 5)
|
||||
{
|
||||
return null;
|
||||
return new Os2Version2To4OpenTypeTable(header, version, xAvgCharWidth,
|
||||
weightClass, widthClass, typeFlags, ySubscriptXSize,
|
||||
ySubscriptYSize,
|
||||
ySubscriptXOffset,
|
||||
ySubscriptYOffset,
|
||||
ySuperscriptXSize,
|
||||
ySuperscriptYSize,
|
||||
ySuperscriptXOffset,
|
||||
ySuperscriptYOffset,
|
||||
yStrikeoutSize,
|
||||
yStrikeoutPosition,
|
||||
familyClass,
|
||||
panose,
|
||||
unicodeCharRange,
|
||||
Encoding.Unicode.GetString(vendorId),
|
||||
selectionFlags,
|
||||
firstCharacterIndex,
|
||||
lastCharacterIndex,
|
||||
sTypoAscender,
|
||||
sTypoDescender,
|
||||
sTypoLineGap,
|
||||
usWinAscent,
|
||||
usWinDescent,
|
||||
ulCodePageRange1,
|
||||
ulCodePageRange2,
|
||||
sxHeight,
|
||||
sCapHeight,
|
||||
usDefaultChar,
|
||||
usBreakChar,
|
||||
usMaxContext);
|
||||
}
|
||||
|
||||
var usLowerOpticalPointSize = data.ReadUnsignedShort();
|
||||
var usUpperOpticalPointSize = data.ReadUnsignedShort();
|
||||
|
||||
|
||||
return null;
|
||||
|
||||
return new Os2Version5OpenTypeTable(header, version, xAvgCharWidth,
|
||||
weightClass, widthClass, typeFlags, ySubscriptXSize,
|
||||
ySubscriptYSize,
|
||||
ySubscriptXOffset,
|
||||
ySubscriptYOffset,
|
||||
ySuperscriptXSize,
|
||||
ySuperscriptYSize,
|
||||
ySuperscriptXOffset,
|
||||
ySuperscriptYOffset,
|
||||
yStrikeoutSize,
|
||||
yStrikeoutPosition,
|
||||
familyClass,
|
||||
panose,
|
||||
unicodeCharRange,
|
||||
Encoding.Unicode.GetString(vendorId),
|
||||
selectionFlags,
|
||||
firstCharacterIndex,
|
||||
lastCharacterIndex,
|
||||
sTypoAscender,
|
||||
sTypoDescender,
|
||||
sTypoLineGap,
|
||||
usWinAscent,
|
||||
usWinDescent,
|
||||
ulCodePageRange1,
|
||||
ulCodePageRange2,
|
||||
sxHeight,
|
||||
sCapHeight,
|
||||
usDefaultChar,
|
||||
usBreakChar,
|
||||
usMaxContext,
|
||||
usLowerOpticalPointSize,
|
||||
usUpperOpticalPointSize);
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,9 +5,16 @@
|
||||
|
||||
internal static class TableParser
|
||||
{
|
||||
private static readonly NameTableParser NameTableParser = new NameTableParser();
|
||||
private static readonly Os2TableParser Os2TableParser = new Os2TableParser();
|
||||
|
||||
public static T Parse<T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITable
|
||||
{
|
||||
if (typeof(T) == typeof(NameTable))
|
||||
{
|
||||
return (T) (object) NameTableParser.Parse(table, data, register);
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(Os2Table))
|
||||
{
|
||||
return (T)(object)Os2TableParser.Parse(table, data, register);
|
||||
|
@@ -92,7 +92,7 @@
|
||||
|
||||
if (tables.TryGetValue(TrueTypeHeaderTable.Name, out var nameTable))
|
||||
{
|
||||
builder.NameTable = NameTable.Load(data, nameTable);
|
||||
builder.NameTable = TableParser.Parse<NameTable>(nameTable, data, builder);
|
||||
}
|
||||
|
||||
if (tables.TryGetValue(TrueTypeHeaderTable.Os2, out var os2Table))
|
||||
|
@@ -1,12 +1,10 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Tables
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Names;
|
||||
using Util;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
internal class NameTable
|
||||
internal class NameTable : ITable
|
||||
{
|
||||
public string Tag => TrueTypeHeaderTable.Name;
|
||||
|
||||
@@ -33,144 +31,30 @@
|
||||
NameRecords = nameRecords;
|
||||
}
|
||||
|
||||
internal static NameTable Load(TrueTypeDataBytes data, TrueTypeHeaderTable table)
|
||||
{
|
||||
data.Seek(table.Offset);
|
||||
// ReSharper disable once UnusedVariable
|
||||
var format = data.ReadUnsignedShort();
|
||||
var count = data.ReadUnsignedShort();
|
||||
var stringOffset = data.ReadUnsignedShort();
|
||||
|
||||
var names = new NameRecordBuilder[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
names[i] = NameRecordBuilder.Read(data);
|
||||
}
|
||||
|
||||
var strings = new TrueTypeNameRecord[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var nameRecord = names[i];
|
||||
|
||||
var encoding = OtherEncodings.Iso88591;
|
||||
|
||||
switch (nameRecord.PlatformId)
|
||||
{
|
||||
case TrueTypePlatformIdentifier.Windows:
|
||||
{
|
||||
var platformEncoding = (TrueTypeWindowsEncodingIdentifier) nameRecord.PlatformEncodingId;
|
||||
|
||||
if (platformEncoding == TrueTypeWindowsEncodingIdentifier.Symbol
|
||||
|| platformEncoding == TrueTypeWindowsEncodingIdentifier.UnicodeBmp)
|
||||
{
|
||||
encoding = Encoding.BigEndianUnicode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TrueTypePlatformIdentifier.Unicode:
|
||||
{
|
||||
encoding = Encoding.BigEndianUnicode;
|
||||
break;
|
||||
}
|
||||
case TrueTypePlatformIdentifier.Iso:
|
||||
{
|
||||
switch (nameRecord.PlatformEncodingId)
|
||||
{
|
||||
case 0:
|
||||
encoding = Encoding.GetEncoding("US-ASCII");
|
||||
break;
|
||||
case 1:
|
||||
encoding = Encoding.GetEncoding("ISO-10646-UCS-2");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var position = table.Offset + stringOffset + nameRecord.Offset;
|
||||
|
||||
data.Seek(position);
|
||||
|
||||
var str = data.ReadString(nameRecord.Length, encoding);
|
||||
|
||||
strings[i] = nameRecord.ToNameRecord(str);
|
||||
}
|
||||
|
||||
return new NameTable(table, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PostScript name for the font if specified, preferring the Windows platform name if present.
|
||||
/// </summary>
|
||||
/// <returns>The PostScript name for the font if found or <see langword="null"/>.</returns>
|
||||
[CanBeNull]
|
||||
private static string GetName(int nameId, TrueTypeNameRecord[] names)
|
||||
public string GetPostscriptName()
|
||||
{
|
||||
const int windowsEnUs = 409;
|
||||
string windows = null;
|
||||
string any = null;
|
||||
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
foreach (var nameRecord in NameRecords)
|
||||
{
|
||||
var name = names[i];
|
||||
|
||||
if (name.NameId != nameId)
|
||||
if (nameRecord.NameId != 6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.PlatformId == TrueTypePlatformIdentifier.Windows && name.LanguageId == windowsEnUs)
|
||||
if (nameRecord.PlatformId == TrueTypePlatformIdentifier.Windows)
|
||||
{
|
||||
return name.Value;
|
||||
return nameRecord.Value;
|
||||
}
|
||||
|
||||
if (name.PlatformId == TrueTypePlatformIdentifier.Windows)
|
||||
{
|
||||
windows = name.Value;
|
||||
}
|
||||
|
||||
any = name.Value;
|
||||
any = nameRecord.Value;
|
||||
}
|
||||
|
||||
return windows ?? any;
|
||||
}
|
||||
|
||||
private struct NameRecordBuilder
|
||||
{
|
||||
public TrueTypePlatformIdentifier PlatformId { get; }
|
||||
|
||||
public int PlatformEncodingId { get; }
|
||||
|
||||
private int LanguageId { get; }
|
||||
|
||||
private int NameId { get; }
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public int Offset { get; }
|
||||
|
||||
private NameRecordBuilder(int platformId, int platformEncodingId, int languageId, int nameId, int length, int offset)
|
||||
{
|
||||
PlatformId = (TrueTypePlatformIdentifier)platformId;
|
||||
PlatformEncodingId = platformEncodingId;
|
||||
LanguageId = languageId;
|
||||
NameId = nameId;
|
||||
Length = length;
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
public TrueTypeNameRecord ToNameRecord(string s)
|
||||
{
|
||||
return new TrueTypeNameRecord(PlatformId, PlatformEncodingId,
|
||||
LanguageId, NameId, Length, Offset, s);
|
||||
}
|
||||
|
||||
public static NameRecordBuilder Read(TrueTypeDataBytes data)
|
||||
{
|
||||
return new NameRecordBuilder(data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort(),
|
||||
data.ReadUnsignedShort());
|
||||
}
|
||||
return any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -10,12 +10,12 @@
|
||||
/// <summary>
|
||||
/// This field is used to specify the code pages encompassed by the font file in the 'cmap' subtable for the Microsoft platform(3), Unicode BMP encoding (1).
|
||||
/// </summary>
|
||||
public byte CodePage1 { get; }
|
||||
public uint CodePage1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This field is the second byte used to specify the code pages encompassed by the font file in the 'cmap' subtable for the Microsoft platform(3), Unicode BMP encoding (1).
|
||||
/// </summary>
|
||||
public byte CodePage2 { get; }
|
||||
public uint CodePage2 { get; }
|
||||
|
||||
public Os2Version1Table(TrueTypeHeaderTable directoryTable, ushort version, short xAverageCharacterWidth, ushort weightClass, ushort widthClass,
|
||||
ushort typeFlags,
|
||||
@@ -41,8 +41,8 @@
|
||||
short typographicLineGap,
|
||||
ushort windowsAscent,
|
||||
ushort windowsDescent,
|
||||
byte codePage1,
|
||||
byte codePage2) : base(directoryTable, version, xAverageCharacterWidth, weightClass, widthClass,
|
||||
uint codePage1,
|
||||
uint codePage2) : base(directoryTable, version, xAverageCharacterWidth, weightClass, widthClass,
|
||||
typeFlags, ySubscriptXSize, ySubscriptYSize, ySubscriptXOffset, ySubscriptYOffset, ySuperscriptXSize,
|
||||
ySuperscriptYSize, ySuperscriptXOffset, ySuperscriptYOffset, yStrikeoutSize, yStrikeoutPosition,
|
||||
familyClass, panose, unicodeRanges, vendorId, fontSelectionFlags, firstCharacterIndex, lastCharacterIndex,
|
||||
|
@@ -69,8 +69,8 @@
|
||||
short typographicLineGap,
|
||||
ushort windowsAscent,
|
||||
ushort windowsDescent,
|
||||
byte codePage1,
|
||||
byte codePage2,
|
||||
uint codePage1,
|
||||
uint codePage2,
|
||||
short xHeight,
|
||||
short capHeight,
|
||||
ushort defaultCharacter,
|
||||
|
@@ -52,8 +52,8 @@
|
||||
short typographicLineGap,
|
||||
ushort windowsAscent,
|
||||
ushort windowsDescent,
|
||||
byte codePage1,
|
||||
byte codePage2,
|
||||
uint codePage1,
|
||||
uint codePage2,
|
||||
short xHeight,
|
||||
short capHeight,
|
||||
ushort defaultCharacter,
|
||||
|
@@ -237,7 +237,7 @@
|
||||
{
|
||||
IToken previousToken = null;
|
||||
|
||||
var dictionary = new Dictionary<IToken, IToken>();
|
||||
var dictionary = new Dictionary<NameToken, IToken>();
|
||||
|
||||
// Skip the operators "dup" etc to reach "begin".
|
||||
while (scanner.MoveNext() && (!(scanner.CurrentToken is OperatorToken operatorToken) || operatorToken.Data != "begin"))
|
||||
|
@@ -1,7 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Tokenization
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using ContentStream;
|
||||
using Exceptions;
|
||||
using IO;
|
||||
using Parser.Parts;
|
||||
using Scanner;
|
||||
@@ -58,19 +58,24 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Dictionary<IToken, IToken> ConvertToDictionary(IReadOnlyList<IToken> tokens)
|
||||
private static Dictionary<NameToken, IToken> ConvertToDictionary(IReadOnlyList<IToken> tokens)
|
||||
{
|
||||
var result = new Dictionary<IToken, IToken>();
|
||||
var result = new Dictionary<NameToken, IToken>();
|
||||
|
||||
IToken key = null;
|
||||
NameToken key = null;
|
||||
for (var i = 0; i < tokens.Count; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
|
||||
if (key == null)
|
||||
{
|
||||
key = token;
|
||||
continue;
|
||||
if (token is NameToken name)
|
||||
{
|
||||
key = name;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new PdfDocumentFormatException($"Expected name as dictionary key, instead got: " + token);
|
||||
}
|
||||
|
||||
// Combine indirect references, e.g. 12 0 R
|
||||
|
@@ -24,26 +24,18 @@
|
||||
/// Create a new <see cref="DictionaryToken"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">The data this dictionary will contain.</param>
|
||||
public DictionaryToken([NotNull]IReadOnlyDictionary<IToken, IToken> data)
|
||||
public DictionaryToken([NotNull]IReadOnlyDictionary<NameToken, IToken> data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
|
||||
var result = new Dictionary<string, IToken>(data.Count);
|
||||
|
||||
foreach (var keyValuePair in data)
|
||||
{
|
||||
if (keyValuePair.Key is NameToken name)
|
||||
{
|
||||
result[name.Data] = keyValuePair.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For now:
|
||||
throw new PdfDocumentFormatException($"Key for dictionary token was not a name! {keyValuePair.Key}");
|
||||
}
|
||||
result[keyValuePair.Key.Data] = keyValuePair.Value;
|
||||
}
|
||||
|
||||
Data = result;
|
||||
@@ -68,7 +60,7 @@
|
||||
|
||||
return typedToken;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Try and get the entry with a given name.
|
||||
/// </summary>
|
||||
|
@@ -113,14 +113,14 @@
|
||||
fontsWritten.Add(font.Key, fontObj);
|
||||
}
|
||||
|
||||
var resources = new Dictionary<IToken, IToken>
|
||||
var resources = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.ProcSet, new ArrayToken(new []{ NameToken.Create("PDF"), NameToken.Create("Text") }) }
|
||||
};
|
||||
|
||||
if (fontsWritten.Count > 0)
|
||||
{
|
||||
var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => ((IToken)fonts[x.Key].FontKey.Name, (IToken)new IndirectReferenceToken(x.Value.Number)))
|
||||
var fontsDictionary = new DictionaryToken(fontsWritten.Select(x => (fonts[x.Key].FontKey.Name, (IToken)new IndirectReferenceToken(x.Value.Number)))
|
||||
.ToDictionary(x => x.Item1, x => x.Item2));
|
||||
|
||||
var fontsDictionaryRef = context.WriteObject(memory, fontsDictionary);
|
||||
@@ -131,7 +131,7 @@
|
||||
var pageReferences = new List<IndirectReferenceToken>();
|
||||
foreach (var page in pages)
|
||||
{
|
||||
var pageDictionary = new Dictionary<IToken, IToken>
|
||||
var pageDictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{NameToken.Type, NameToken.Page},
|
||||
{
|
||||
@@ -155,7 +155,7 @@
|
||||
pageReferences.Add(new IndirectReferenceToken(pageRef.Number));
|
||||
}
|
||||
|
||||
var pagesDictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var pagesDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Type, NameToken.Pages },
|
||||
{ NameToken.Kids, new ArrayToken(pageReferences) },
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
var pagesRef = context.WriteObject(memory, pagesDictionary);
|
||||
|
||||
var catalog = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var catalog = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Type, NameToken.Catalog },
|
||||
{ NameToken.Pages, new IndirectReferenceToken(pagesRef.Number) }
|
||||
@@ -189,7 +189,7 @@
|
||||
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
var streamDictionary = new Dictionary<IToken, IToken>
|
||||
var streamDictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Length, new NumericToken(bytes.Length) }
|
||||
};
|
||||
|
@@ -35,7 +35,7 @@
|
||||
|
||||
public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
|
||||
{
|
||||
var dictionary = new Dictionary<IToken, IToken>
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Type, NameToken.Font },
|
||||
{ NameToken.Subtype, NameToken.Type1 },
|
||||
|
@@ -150,7 +150,7 @@
|
||||
outputStream.Write(Trailer, 0, Trailer.Length);
|
||||
WriteLineBreak(outputStream);
|
||||
|
||||
var trailerDictionary = new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var trailerDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{NameToken.Size, new NumericToken(objectOffsets.Count) },
|
||||
{NameToken.Root, new IndirectReferenceToken(catalogToken.Number) }
|
||||
|
@@ -2,8 +2,11 @@
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Fonts;
|
||||
using Fonts.Exceptions;
|
||||
using Fonts.TrueType;
|
||||
using Fonts.TrueType.Tables;
|
||||
using Geometry;
|
||||
using Tokens;
|
||||
|
||||
@@ -28,14 +31,14 @@
|
||||
public ObjectToken WriteFont(NameToken fontKeyName, Stream outputStream, BuilderContext context)
|
||||
{
|
||||
var bytes = fontFileBytes;
|
||||
var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary<IToken, IToken>
|
||||
var embeddedFile = new StreamToken(new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Length, new NumericToken(bytes.Count) }
|
||||
}), bytes);
|
||||
|
||||
var fileRef = context.WriteObject(outputStream, embeddedFile);
|
||||
|
||||
var baseFont = NameToken.Create(font.TableRegister.NameTable.FontName);
|
||||
var baseFont = NameToken.Create(font.TableRegister.NameTable.GetPostscriptName());
|
||||
|
||||
var postscript = font.TableRegister.PostScriptTable;
|
||||
var hhead = font.TableRegister.HorizontalHeaderTable;
|
||||
@@ -60,11 +63,36 @@
|
||||
{ NameToken.FontFile2, new IndirectReferenceToken(fileRef.Number) }
|
||||
};
|
||||
|
||||
var dictionary = new Dictionary<IToken, IToken>
|
||||
var os2 = font.TableRegister.Os2Table;
|
||||
if (os2 == null)
|
||||
{
|
||||
throw new InvalidFontFormatException("Embedding TrueType font requires OS/2 table.");
|
||||
}
|
||||
|
||||
if (os2 is Os2Version2To4OpenTypeTable twoPlus)
|
||||
{
|
||||
descriptorDictionary[NameToken.CapHeight] = new NumericToken(twoPlus.CapHeight);
|
||||
descriptorDictionary[NameToken.Xheight] = new NumericToken(twoPlus.XHeight);
|
||||
}
|
||||
|
||||
descriptorDictionary[NameToken.StemV] = new NumericToken(bbox.Width * scaling * 0.13m);
|
||||
|
||||
var widths = font.TableRegister.GlyphTable.Glyphs.Select(x => new NumericToken(x.Bounds.Width)).ToArray();
|
||||
|
||||
var widthsRef = context.WriteObject(outputStream, new ArrayToken(widths));
|
||||
|
||||
var descriptor = context.WriteObject(outputStream, new DictionaryToken(descriptorDictionary));
|
||||
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Type, NameToken.Font },
|
||||
{ NameToken.Subtype, NameToken.TrueType },
|
||||
{ NameToken.BaseFont, baseFont },
|
||||
{ NameToken.FontDescriptor, new IndirectReferenceToken(descriptor.Number) },
|
||||
{ NameToken.FirstChar, new NumericToken(0) },
|
||||
{ NameToken.LastChar, new NumericToken(font.TableRegister.GlyphTable.Glyphs.Count - 1) },
|
||||
{ NameToken.Widths, new IndirectReferenceToken(widthsRef.Number) },
|
||||
{ NameToken.Encoding, NameToken.WinAnsiEncoding }
|
||||
};
|
||||
|
||||
var token = new DictionaryToken(dictionary);
|
||||
|
Reference in New Issue
Block a user