#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:
Eliot Jones
2018-11-18 16:32:28 +00:00
parent 530410c996
commit 2c159f71e8
12 changed files with 314 additions and 160 deletions

View File

@@ -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)

View File

@@ -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)
{ {

View File

@@ -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;
}
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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));
} }

View File

@@ -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)

View File

@@ -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
{ {

View File

@@ -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;

View File

@@ -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);