mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-21 04:17:57 +08:00
#6 rename some cff classes, change protection levels and start fixing bugs with charstrings which include hints in routine calls
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using PdfPig.Fonts.CompactFontFormat;
|
using PdfPig.Fonts.CompactFontFormat;
|
||||||
|
using PdfPig.Fonts.CompactFontFormat.CharStrings;
|
||||||
using PdfPig.Fonts.CompactFontFormat.Dictionaries;
|
using PdfPig.Fonts.CompactFontFormat.Dictionaries;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -20,7 +21,69 @@
|
|||||||
|
|
||||||
var font = parser.Parse(new CompactFontFormatData(fileBytes));
|
var font = parser.Parse(new CompactFontFormatData(fileBytes));
|
||||||
|
|
||||||
Assert.Equal("MinionPro", font.ToString());
|
Assert.Equal(1, font.Header.MajorVersion);
|
||||||
|
Assert.Equal(1, font.Fonts.Count);
|
||||||
|
Assert.True(font.Fonts.ContainsKey("MinionPro-It"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanInterpretPercentSymbol()
|
||||||
|
{
|
||||||
|
var fileBytes = GetFileBytes("MinionPro.bin");
|
||||||
|
|
||||||
|
var font = parser.Parse(new CompactFontFormatData(fileBytes));
|
||||||
|
|
||||||
|
// Calls a global subroutine
|
||||||
|
var box = font.GetCharacterBoundingBox("percent");
|
||||||
|
|
||||||
|
Assert.NotNull(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanInterpretNumberSignSymbol()
|
||||||
|
{
|
||||||
|
var fileBytes = GetFileBytes("MinionPro.bin");
|
||||||
|
|
||||||
|
var font = parser.Parse(new CompactFontFormatData(fileBytes));
|
||||||
|
|
||||||
|
// Calls a local subroutine
|
||||||
|
var box = font.GetCharacterBoundingBox("numbersign");
|
||||||
|
|
||||||
|
Assert.NotNull(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanInterpretPerThousandSymbol()
|
||||||
|
{
|
||||||
|
var fileBytes = GetFileBytes("MinionPro.bin");
|
||||||
|
|
||||||
|
var font = parser.Parse(new CompactFontFormatData(fileBytes));
|
||||||
|
|
||||||
|
// Calls a local subroutine which adds to the hints
|
||||||
|
var box = font.GetCharacterBoundingBox("perthousand");
|
||||||
|
|
||||||
|
Assert.NotNull(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanInterpretAllGlyphs()
|
||||||
|
{
|
||||||
|
var fileBytes = GetFileBytes("MinionPro.bin");
|
||||||
|
|
||||||
|
var fontSet = parser.Parse(new CompactFontFormatData(fileBytes));
|
||||||
|
|
||||||
|
var font = fontSet.Fonts["MinionPro-It"];
|
||||||
|
|
||||||
|
var charStrings = default(Type2CharStrings);
|
||||||
|
font.CharStrings.Match(x => throw new InvalidOperationException("The charstrings in MinionPro are Type 2."),
|
||||||
|
x => charStrings = x);
|
||||||
|
|
||||||
|
foreach (var charString in charStrings.CharStrings)
|
||||||
|
{
|
||||||
|
var path = charStrings.Generate(charString.Key);
|
||||||
|
|
||||||
|
Assert.NotNull(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] GetFileBytes(string name)
|
private static byte[] GetFileBytes(string name)
|
||||||
|
@@ -116,7 +116,7 @@
|
|||||||
return CountToBias(count);
|
return CountToBias(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountToBias(int count)
|
public static int CountToBias(int count)
|
||||||
{
|
{
|
||||||
if (count < 1240)
|
if (count < 1240)
|
||||||
{
|
{
|
||||||
|
@@ -386,12 +386,12 @@
|
|||||||
ctx.Stack.Clear();
|
ctx.Stack.Clear();
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ 29, new LazyType2Command("callgsubr", ctx =>
|
{ 29, new LazyType2Command("callgsubr", ctx =>
|
||||||
{
|
{
|
||||||
var index = (int)ctx.Stack.PopTop();
|
var index = (int)ctx.Stack.PopTop();
|
||||||
var bias = ctx.GetLocalSubroutineBias();
|
var bias = ctx.GetGlobalSubroutineBias();
|
||||||
var actualIndex = index + bias;
|
var actualIndex = index + bias;
|
||||||
var subr = ctx.LocalSubroutines[actualIndex];
|
var subr = ctx.GlobalSubroutines[actualIndex];
|
||||||
ctx.EvaluateSubroutine(subr);
|
ctx.EvaluateSubroutine(subr);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -711,34 +711,36 @@
|
|||||||
throw new ArgumentNullException(nameof(globalSubroutines));
|
throw new ArgumentNullException(nameof(globalSubroutines));
|
||||||
}
|
}
|
||||||
|
|
||||||
var localSubroutineSequences = new Dictionary<int, Type2CharStrings.CommandSequence>();
|
|
||||||
for (var i = 0; i < localSubroutines.Count; i++)
|
|
||||||
{
|
|
||||||
var bytes = localSubroutines[i];
|
|
||||||
var sequence = ParseSingle(bytes);
|
|
||||||
localSubroutineSequences[i] = new Type2CharStrings.CommandSequence(sequence);
|
|
||||||
}
|
|
||||||
|
|
||||||
var globalSubroutineSequences = new Dictionary<int, Type2CharStrings.CommandSequence>();
|
var globalSubroutineSequences = new Dictionary<int, Type2CharStrings.CommandSequence>();
|
||||||
for (var i = 0; i < globalSubroutines.Count; i++)
|
for (var i = 0; i < globalSubroutines.Count; i++)
|
||||||
{
|
{
|
||||||
var bytes = globalSubroutines[i];
|
var bytes = globalSubroutines[i];
|
||||||
var sequence = ParseSingle(bytes);
|
var sequence = ParseSingle(bytes, null, null);
|
||||||
globalSubroutineSequences[i] = new Type2CharStrings.CommandSequence(sequence);
|
globalSubroutineSequences[i] = new Type2CharStrings.CommandSequence(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localSubroutineSequences = new Dictionary<int, Type2CharStrings.CommandSequence>();
|
||||||
|
for (var i = 0; i < localSubroutines.Count; i++)
|
||||||
|
{
|
||||||
|
var bytes = localSubroutines[i];
|
||||||
|
var sequence = ParseSingle(bytes, null, globalSubroutineSequences);
|
||||||
|
localSubroutineSequences[i] = new Type2CharStrings.CommandSequence(sequence);
|
||||||
|
}
|
||||||
|
|
||||||
var charStrings = new Dictionary<string, Type2CharStrings.CommandSequence>();
|
var charStrings = new Dictionary<string, Type2CharStrings.CommandSequence>();
|
||||||
for (var i = 0; i < charStringBytes.Count; i++)
|
for (var i = 0; i < charStringBytes.Count; i++)
|
||||||
{
|
{
|
||||||
var charString = charStringBytes[i];
|
var charString = charStringBytes[i];
|
||||||
var sequence = ParseSingle(charString);
|
var sequence = ParseSingle(charString, localSubroutineSequences, globalSubroutineSequences);
|
||||||
charStrings[charset.GetNameByGlyphId(i)] = new Type2CharStrings.CommandSequence(sequence);
|
charStrings[charset.GetNameByGlyphId(i)] = new Type2CharStrings.CommandSequence(sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Type2CharStrings(charStrings, localSubroutineSequences, globalSubroutineSequences);
|
return new Type2CharStrings(charStrings, localSubroutineSequences, globalSubroutineSequences);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<Union<decimal, LazyType2Command>> ParseSingle(IReadOnlyList<byte> bytes)
|
private static IReadOnlyList<Union<decimal, LazyType2Command>> ParseSingle(IReadOnlyList<byte> bytes,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> localSubroutines,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> globalSubroutines)
|
||||||
{
|
{
|
||||||
var instructions = new List<Union<decimal, LazyType2Command>>();
|
var instructions = new List<Union<decimal, LazyType2Command>>();
|
||||||
for (var i = 0; i < bytes.Count; i++)
|
for (var i = 0; i < bytes.Count; i++)
|
||||||
@@ -746,7 +748,7 @@
|
|||||||
var b = bytes[i];
|
var b = bytes[i];
|
||||||
if (b <= 31 && b != 28)
|
if (b <= 31 && b != 28)
|
||||||
{
|
{
|
||||||
var command = GetCommand(b, bytes, instructions, ref i);
|
var command = GetCommand(b, bytes, instructions, localSubroutines, globalSubroutines, ref i);
|
||||||
instructions.Add(Union<decimal, LazyType2Command>.Two(command));
|
instructions.Add(Union<decimal, LazyType2Command>.Two(command));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -800,7 +802,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static LazyType2Command GetCommand(byte b, IReadOnlyList<byte> bytes,
|
private static LazyType2Command GetCommand(byte b, IReadOnlyList<byte> bytes,
|
||||||
IReadOnlyList<Union<decimal, LazyType2Command>> precedingCommands, ref int i)
|
IReadOnlyList<Union<decimal, LazyType2Command>> precedingCommands,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> localSubroutines,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> globalSubroutines, ref int i)
|
||||||
{
|
{
|
||||||
if (b == 12)
|
if (b == 12)
|
||||||
{
|
{
|
||||||
@@ -816,50 +820,9 @@
|
|||||||
// hintmask and cntrmask
|
// hintmask and cntrmask
|
||||||
if (b == 19 || b == 20)
|
if (b == 19 || b == 20)
|
||||||
{
|
{
|
||||||
/*
|
var minimumFullBytes = CalculatePrecedingHintBytes(precedingCommands, localSubroutines, globalSubroutines);
|
||||||
* The hintmask operator is followed by one or more data bytes that specify the stem hints which are to be active for the
|
|
||||||
* subsequent path construction. The number of data bytes must be exactly the number needed to represent the number of
|
|
||||||
* stems in the original stem list (those stems specified by the hstem, vstem, hstemhm, or vstemhm commands), using one bit
|
|
||||||
* in the data bytes for each stem in the original stem list.
|
|
||||||
*/
|
|
||||||
var stemCount = 0;
|
|
||||||
var precedingNumbers = 0;
|
|
||||||
var hasEncounteredInitialHintMask = false;
|
|
||||||
for (var j = 0; j < precedingCommands.Count; j++)
|
|
||||||
{
|
|
||||||
var item = precedingCommands[j];
|
|
||||||
item.Match(x => precedingNumbers++,
|
|
||||||
x =>
|
|
||||||
{
|
|
||||||
// The numbers preceding the first hintmask following hinting can act as vertical hints.
|
|
||||||
if (x.Name == "hintmask" && !hasEncounteredInitialHintMask)
|
|
||||||
{
|
|
||||||
hasEncounteredInitialHintMask = true;
|
|
||||||
stemCount += precedingNumbers / 2;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!HintingCommandNames.Contains(x.Name))
|
|
||||||
{
|
|
||||||
precedingNumbers = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stemCount += precedingNumbers / 2;
|
|
||||||
precedingNumbers = 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var fullStemCount = stemCount;
|
|
||||||
// The vstem command can be left out, e.g. for 12 20 hstemhm 4 6 hintmask, 4 and 6 act as the vertical hints
|
|
||||||
if (precedingNumbers > 0 && !hasEncounteredInitialHintMask)
|
|
||||||
{
|
|
||||||
fullStemCount += precedingNumbers / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
var minimumFullBytes = Math.Ceiling(fullStemCount / 8d);
|
|
||||||
// Skip the following hintmask or cntrmask data bytes
|
// Skip the following hintmask or cntrmask data bytes
|
||||||
i += (int)minimumFullBytes;
|
i += minimumFullBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SingleByteCommandStore.TryGetValue(b, out var command))
|
if (SingleByteCommandStore.TryGetValue(b, out var command))
|
||||||
@@ -869,5 +832,130 @@
|
|||||||
|
|
||||||
return new LazyType2Command($"unknown: {b}", x => { });
|
return new LazyType2Command($"unknown: {b}", x => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int CalculatePrecedingHintBytes(IReadOnlyList<Union<decimal, LazyType2Command>> precedingCommands,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> localSubroutines,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> globalSubroutines)
|
||||||
|
{
|
||||||
|
// Do a first pass to substitute in all local and global subroutines prior to the first hintmask.
|
||||||
|
var commandsToCountHints = BuildFullCommandSequence(precedingCommands, localSubroutines, globalSubroutines);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The hintmask operator is followed by one or more data bytes that specify the stem hints which are to be active for the
|
||||||
|
* subsequent path construction. The number of data bytes must be exactly the number needed to represent the number of
|
||||||
|
* stems in the original stem list (those stems specified by the hstem, vstem, hstemhm, or vstemhm commands), using one bit
|
||||||
|
* in the data bytes for each stem in the original stem list.
|
||||||
|
*/
|
||||||
|
var stemCount = 0;
|
||||||
|
var precedingNumbers = 0;
|
||||||
|
var hasEncounteredInitialHintMask = false;
|
||||||
|
for (var j = 0; j < commandsToCountHints.Count; j++)
|
||||||
|
{
|
||||||
|
var item = commandsToCountHints[j];
|
||||||
|
item.Match(x =>
|
||||||
|
{
|
||||||
|
precedingNumbers++;
|
||||||
|
},
|
||||||
|
x =>
|
||||||
|
{
|
||||||
|
// The numbers preceding the first hintmask following hinting can act as vertical hints.
|
||||||
|
if (x.Name == "hintmask" && !hasEncounteredInitialHintMask)
|
||||||
|
{
|
||||||
|
hasEncounteredInitialHintMask = true;
|
||||||
|
stemCount += precedingNumbers / 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HintingCommandNames.Contains(x.Name))
|
||||||
|
{
|
||||||
|
precedingNumbers = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stemCount += precedingNumbers / 2;
|
||||||
|
precedingNumbers = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullStemCount = stemCount;
|
||||||
|
// The vstem command can be left out, e.g. for 12 20 hstemhm 4 6 hintmask, 4 and 6 act as the vertical hints
|
||||||
|
if (precedingNumbers > 0 && !hasEncounteredInitialHintMask)
|
||||||
|
{
|
||||||
|
fullStemCount += precedingNumbers / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minimumFullBytes = (int)Math.Ceiling(fullStemCount / 8d);
|
||||||
|
|
||||||
|
return minimumFullBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<Union<decimal, LazyType2Command>> BuildFullCommandSequence(IReadOnlyList<Union<decimal, LazyType2Command>> allNonSubroutineCommands,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> localSubroutines,
|
||||||
|
Dictionary<int, Type2CharStrings.CommandSequence> globalSubroutines)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Since the initial hintmask or stem information may be in a subroutine we must include subroutines
|
||||||
|
* when calculating the overall number of hints. This is a nuisance because we have to evaluate the
|
||||||
|
* charstring but should be rare enough to avoid a performance hit.
|
||||||
|
*/
|
||||||
|
bool IsSubroutine(LazyType2Command cmd)
|
||||||
|
{
|
||||||
|
return cmd.Name == "callsubr" || cmd.Name == "callgsubr";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localSubroutines == null || globalSubroutines == null)
|
||||||
|
{
|
||||||
|
return allNonSubroutineCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a mutable list of the commands to substitute subroutine into.
|
||||||
|
var results = new List<Union<decimal, LazyType2Command>>(allNonSubroutineCommands);
|
||||||
|
|
||||||
|
var firstHintmask = false;
|
||||||
|
|
||||||
|
var previousNumber = -1m;
|
||||||
|
for (var i = 0; i < results.Count; i++)
|
||||||
|
{
|
||||||
|
var command = results[i];
|
||||||
|
|
||||||
|
var foundSubroutine = false;
|
||||||
|
var wasLocalSubroutine = false;
|
||||||
|
var subroutineIndex = 0;
|
||||||
|
|
||||||
|
command.Match(x => previousNumber = x, x =>
|
||||||
|
{
|
||||||
|
// When we have a subroutine which appears before a hintmask we substitute the actual commands in to check for hints.
|
||||||
|
if (IsSubroutine(x) && !firstHintmask)
|
||||||
|
{
|
||||||
|
foundSubroutine = true;
|
||||||
|
wasLocalSubroutine = x.Name == "callsubr";
|
||||||
|
|
||||||
|
var bias = Type2BuildCharContext.CountToBias(wasLocalSubroutine ? localSubroutines.Count : globalSubroutines.Count);
|
||||||
|
subroutineIndex = (int) previousNumber + bias;
|
||||||
|
}
|
||||||
|
else if (x.Name == "hintmask")
|
||||||
|
{
|
||||||
|
firstHintmask = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundSubroutine)
|
||||||
|
{
|
||||||
|
// Replace the call to the local or global subroutine with the actual routine.
|
||||||
|
var routine = wasLocalSubroutine ? localSubroutines[subroutineIndex] : globalSubroutines[subroutineIndex];
|
||||||
|
results.RemoveAt(i);
|
||||||
|
results.InsertRange(i , routine.Commands);
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit once we hit the first hintmask since all hints have now been declared.
|
||||||
|
if (firstHintmask)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,9 +48,16 @@
|
|||||||
throw new InvalidOperationException($"No charstring sequence with the name /{name} in this font.");
|
throw new InvalidOperationException($"No charstring sequence with the name /{name} in this font.");
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph = Run(sequence);
|
try
|
||||||
|
{
|
||||||
|
glyph = Run(sequence);
|
||||||
|
|
||||||
glyphs[name] = glyph;
|
glyphs[name] = glyph;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Failed to interpret charstring for symbol with name: {name}. Commands: {sequence}.", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return glyph;
|
return glyph;
|
||||||
@@ -79,14 +86,14 @@
|
|||||||
case "hstemhm":
|
case "hstemhm":
|
||||||
case "vstemhm":
|
case "vstemhm":
|
||||||
case "vstem":
|
case "vstem":
|
||||||
{
|
|
||||||
var oddArgCount = context.Stack.Length % 2 != 0;
|
|
||||||
if (oddArgCount)
|
|
||||||
{
|
{
|
||||||
context.Width = context.Stack.PopBottom();
|
var oddArgCount = context.Stack.Length % 2 != 0;
|
||||||
|
if (oddArgCount)
|
||||||
|
{
|
||||||
|
context.Width = context.Stack.PopBottom();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "hmoveto":
|
case "hmoveto":
|
||||||
case "vmoveto":
|
case "vmoveto":
|
||||||
SetWidthFromArgumentsIfPresent(context, 1);
|
SetWidthFromArgumentsIfPresent(context, 1);
|
||||||
@@ -98,11 +105,11 @@
|
|||||||
case "hintmask":
|
case "hintmask":
|
||||||
case "endchar:":
|
case "endchar:":
|
||||||
SetWidthFromArgumentsIfPresent(context, 0);
|
SetWidthFromArgumentsIfPresent(context, 0);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
hasRunStackClearingCommand = false;
|
hasRunStackClearingCommand = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,24 +11,24 @@
|
|||||||
internal class CompactFontFormatFont
|
internal class CompactFontFormatFont
|
||||||
{
|
{
|
||||||
public CompactFontFormatTopLevelDictionary TopDictionary { get; }
|
public CompactFontFormatTopLevelDictionary TopDictionary { get; }
|
||||||
private readonly CompactFontFormatPrivateDictionary privateDictionary;
|
public CompactFontFormatPrivateDictionary PrivateDictionary { get; }
|
||||||
private readonly ICompactFontFormatCharset charset;
|
public ICompactFontFormatCharset Charset { get; }
|
||||||
private readonly Union<Type1CharStrings, Type2CharStrings> charStrings;
|
public Union<Type1CharStrings, Type2CharStrings> CharStrings { get; }
|
||||||
|
|
||||||
public CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary, CompactFontFormatPrivateDictionary privateDictionary,
|
public CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary, CompactFontFormatPrivateDictionary privateDictionary,
|
||||||
ICompactFontFormatCharset charset,
|
ICompactFontFormatCharset charset,
|
||||||
Union<Type1CharStrings, Type2CharStrings> charStrings)
|
Union<Type1CharStrings, Type2CharStrings> charStrings)
|
||||||
{
|
{
|
||||||
TopDictionary = topDictionary;
|
TopDictionary = topDictionary;
|
||||||
this.privateDictionary = privateDictionary;
|
PrivateDictionary = privateDictionary;
|
||||||
this.charset = charset;
|
Charset = charset;
|
||||||
this.charStrings = charStrings;
|
CharStrings = charStrings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||||
{
|
{
|
||||||
var result = default(PdfRectangle?);
|
var result = default(PdfRectangle?);
|
||||||
charStrings.Match(x => throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported."),
|
CharStrings.Match(x => throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported."),
|
||||||
x => { result = x.Generate(characterName).GetBoundingRectangle(); });
|
x => { result = x.Generate(characterName).GetBoundingRectangle(); });
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@@ -0,0 +1,62 @@
|
|||||||
|
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Core;
|
||||||
|
using Geometry;
|
||||||
|
using Util.JetBrains.Annotations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A Compact Font Format (CFF) font program as described in The Compact Font Format specification (Adobe Technical Note #5176).
|
||||||
|
/// A CFF font may contain multiple fonts and achieves compression by sharing details between fonts in the set.
|
||||||
|
/// </summary>
|
||||||
|
internal class CompactFontFormatFontProgram
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The decoded header table for this font.
|
||||||
|
/// </summary>
|
||||||
|
public CompactFontFormatHeader Header { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The individual fonts contained in this font keyed by name.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull]
|
||||||
|
public IReadOnlyDictionary<string, CompactFontFormatFont> Fonts { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="CompactFontFormatFontProgram"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="header">The header table for the font.</param>
|
||||||
|
/// <param name="fontSet">The fonts in this font program.</param>
|
||||||
|
public CompactFontFormatFontProgram(CompactFontFormatHeader header, [NotNull] IReadOnlyDictionary<string, CompactFontFormatFont> fontSet)
|
||||||
|
{
|
||||||
|
Header = header;
|
||||||
|
Fonts = fontSet ?? throw new ArgumentNullException(nameof(fontSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TransformationMatrix GetFontTransformationMatrix()
|
||||||
|
{
|
||||||
|
var result = GetFont().TopDictionary.FontMatrix;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||||
|
{
|
||||||
|
var font = GetFont();
|
||||||
|
return font.GetCharacterBoundingBox(characterName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompactFontFormatFont GetFont()
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
// TODO: what to do if there are multiple fonts?
|
||||||
|
if (Fonts.Count > 1)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Multiple fonts in a CFF");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return Fonts.First().Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,47 +0,0 @@
|
|||||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
|
||||||
{
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Core;
|
|
||||||
using Geometry;
|
|
||||||
|
|
||||||
internal class CompactFontFormatFontSet
|
|
||||||
{
|
|
||||||
private readonly CompactFontFormatHeader header;
|
|
||||||
private readonly IReadOnlyList<string> fontNames;
|
|
||||||
private readonly IReadOnlyDictionary<string, CompactFontFormatFont> fontSet;
|
|
||||||
|
|
||||||
public CompactFontFormatFontSet(CompactFontFormatHeader header, IReadOnlyList<string> fontNames,
|
|
||||||
IReadOnlyDictionary<string, CompactFontFormatFont> fontSet)
|
|
||||||
{
|
|
||||||
this.header = header;
|
|
||||||
this.fontNames = fontNames;
|
|
||||||
this.fontSet = fontSet;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TransformationMatrix GetFontTransformationMatrix()
|
|
||||||
{
|
|
||||||
var result = GetFont().TopDictionary.FontMatrix;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
|
||||||
{
|
|
||||||
var font = GetFont();
|
|
||||||
return font.GetCharacterBoundingBox(characterName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompactFontFormatFont GetFont()
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
// TODO: what to do if there are multiple fonts?
|
|
||||||
if (fontSet.Count > 1)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException("Multiple fonts in a CFF");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return fontSet.First().Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -150,28 +150,6 @@
|
|||||||
throw new ArgumentOutOfRangeException($"Unexpected CharString type in CFF font: {topDictionary.CharStringType}.");
|
throw new ArgumentOutOfRangeException($"Unexpected CharString type in CFF font: {topDictionary.CharStringType}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Debugger.IsAttached)
|
|
||||||
{
|
|
||||||
var builder = new StringBuilder("<!DOCTYPE html><html><head></head><body>");
|
|
||||||
foreach (var pair in charStrings.CharStrings)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var path = charStrings.Generate(pair.Key);
|
|
||||||
var svg = path.ToFullSvg();
|
|
||||||
builder.AppendLine(svg);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append("</body></html>");
|
|
||||||
|
|
||||||
File.WriteAllText(@"C:\git\index.html", builder.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union<Type1CharStrings, Type2CharStrings>.Two(charStrings));
|
return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union<Type1CharStrings, Type2CharStrings>.Two(charStrings));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
this.indexReader = indexReader;
|
this.indexReader = indexReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompactFontFormatFontSet Parse(CompactFontFormatData data)
|
public CompactFontFormatFontProgram Parse(CompactFontFormatData data)
|
||||||
{
|
{
|
||||||
var tag = ReadTag(data);
|
var tag = ReadTag(data);
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
fonts[fontName] = individualFontParser.Parse(data, fontName, topLevelDictionaryIndex[i], stringIndex, globalSubroutineIndex);
|
fonts[fontName] = individualFontParser.Parse(data, fontName, topLevelDictionaryIndex[i], stringIndex, globalSubroutineIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CompactFontFormatFontSet(header, fontNames, fonts);
|
return new CompactFontFormatFontProgram(header, fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ReadTag(CompactFontFormatData data)
|
private static string ReadTag(CompactFontFormatData data)
|
||||||
|
@@ -93,7 +93,7 @@
|
|||||||
return new Type1FontSimple(name, firstCharacter, lastCharacter, widths, descriptor, encoding, toUnicodeCMap, font);
|
return new Type1FontSimple(name, firstCharacter, lastCharacter, widths, descriptor, encoding, toUnicodeCMap, font);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Union<Type1FontProgram, CompactFontFormatFontSet> ParseFontProgram(FontDescriptor descriptor, bool isLenientParsing)
|
private Union<Type1FontProgram, CompactFontFormatFontProgram> ParseFontProgram(FontDescriptor descriptor, bool isLenientParsing)
|
||||||
{
|
{
|
||||||
if (descriptor?.FontFile == null)
|
if (descriptor?.FontFile == null)
|
||||||
{
|
{
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
&& NameToken.Type1C.Equals(subTypeName))
|
&& NameToken.Type1C.Equals(subTypeName))
|
||||||
{
|
{
|
||||||
var cffFont = compactFontFormatParser.Parse(new CompactFontFormatData(bytes));
|
var cffFont = compactFontFormatParser.Parse(new CompactFontFormatData(bytes));
|
||||||
return Union<Type1FontProgram, CompactFontFormatFontSet>.Two(cffFont);
|
return Union<Type1FontProgram, CompactFontFormatFontProgram>.Two(cffFont);
|
||||||
}
|
}
|
||||||
|
|
||||||
var length1 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length1, pdfScanner);
|
var length1 = stream.StreamDictionary.Get<NumericToken>(NameToken.Length1, pdfScanner);
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
|
|
||||||
var font = type1FontParser.Parse(new ByteArrayInputBytes(bytes), length1.Int, length2.Int);
|
var font = type1FontParser.Parse(new ByteArrayInputBytes(bytes), length1.Int, length2.Int);
|
||||||
|
|
||||||
return Union<Type1FontProgram, CompactFontFormatFontSet>.One(font);
|
return Union<Type1FontProgram, CompactFontFormatFontProgram>.One(font);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@@ -29,7 +29,7 @@
|
|||||||
private readonly Encoding encoding;
|
private readonly Encoding encoding;
|
||||||
|
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
private readonly Union<Type1FontProgram, CompactFontFormatFontSet> fontProgram;
|
private readonly Union<Type1FontProgram, CompactFontFormatFontProgram> fontProgram;
|
||||||
|
|
||||||
private readonly ToUnicodeCMap toUnicodeCMap;
|
private readonly ToUnicodeCMap toUnicodeCMap;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
|
|
||||||
public Type1FontSimple(NameToken name, int firstChar, int lastChar, decimal[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
public Type1FontSimple(NameToken name, int firstChar, int lastChar, decimal[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
||||||
CMap toUnicodeCMap,
|
CMap toUnicodeCMap,
|
||||||
Union<Type1FontProgram, CompactFontFormatFontSet> fontProgram)
|
Union<Type1FontProgram, CompactFontFormatFontProgram> fontProgram)
|
||||||
{
|
{
|
||||||
this.firstChar = firstChar;
|
this.firstChar = firstChar;
|
||||||
this.lastChar = lastChar;
|
this.lastChar = lastChar;
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
using System;
|
// ReSharper disable InconsistentNaming
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
|
|
||||||
namespace UglyToad.PdfPig.Util
|
namespace UglyToad.PdfPig.Util
|
||||||
{
|
{
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
internal abstract class Union<A, B>
|
internal abstract class Union<A, B>
|
||||||
{
|
{
|
||||||
public abstract void Match(Action<A> first, Action<B> second);
|
public abstract void Match(Action<A> first, Action<B> second);
|
||||||
@@ -28,6 +29,7 @@ namespace UglyToad.PdfPig.Util
|
|||||||
Item = item;
|
Item = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DebuggerStepThrough]
|
||||||
public override void Match(Action<A> first, Action<B> second)
|
public override void Match(Action<A> first, Action<B> second)
|
||||||
{
|
{
|
||||||
first(Item);
|
first(Item);
|
||||||
@@ -48,6 +50,7 @@ namespace UglyToad.PdfPig.Util
|
|||||||
Item = item;
|
Item = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DebuggerStepThrough]
|
||||||
public override void Match(Action<A> first, Action<B> second)
|
public override void Match(Action<A> first, Action<B> second)
|
||||||
{
|
{
|
||||||
second(Item);
|
second(Item);
|
||||||
|
Reference in New Issue
Block a user