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.Linq;
|
||||
using PdfPig.Fonts.CompactFontFormat;
|
||||
using PdfPig.Fonts.CompactFontFormat.CharStrings;
|
||||
using PdfPig.Fonts.CompactFontFormat.Dictionaries;
|
||||
using Xunit;
|
||||
|
||||
@@ -20,7 +21,69 @@
|
||||
|
||||
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)
|
||||
|
@@ -116,7 +116,7 @@
|
||||
return CountToBias(count);
|
||||
}
|
||||
|
||||
private static int CountToBias(int count)
|
||||
public static int CountToBias(int count)
|
||||
{
|
||||
if (count < 1240)
|
||||
{
|
||||
|
@@ -389,9 +389,9 @@
|
||||
{ 29, new LazyType2Command("callgsubr", ctx =>
|
||||
{
|
||||
var index = (int)ctx.Stack.PopTop();
|
||||
var bias = ctx.GetLocalSubroutineBias();
|
||||
var bias = ctx.GetGlobalSubroutineBias();
|
||||
var actualIndex = index + bias;
|
||||
var subr = ctx.LocalSubroutines[actualIndex];
|
||||
var subr = ctx.GlobalSubroutines[actualIndex];
|
||||
ctx.EvaluateSubroutine(subr);
|
||||
})
|
||||
},
|
||||
@@ -711,34 +711,36 @@
|
||||
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>();
|
||||
for (var i = 0; i < globalSubroutines.Count; i++)
|
||||
{
|
||||
var bytes = globalSubroutines[i];
|
||||
var sequence = ParseSingle(bytes);
|
||||
var sequence = ParseSingle(bytes, null, null);
|
||||
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>();
|
||||
for (var i = 0; i < charStringBytes.Count; i++)
|
||||
{
|
||||
var charString = charStringBytes[i];
|
||||
var sequence = ParseSingle(charString);
|
||||
var sequence = ParseSingle(charString, localSubroutineSequences, globalSubroutineSequences);
|
||||
charStrings[charset.GetNameByGlyphId(i)] = new Type2CharStrings.CommandSequence(sequence);
|
||||
}
|
||||
|
||||
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>>();
|
||||
for (var i = 0; i < bytes.Count; i++)
|
||||
@@ -746,7 +748,7 @@
|
||||
var b = bytes[i];
|
||||
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));
|
||||
}
|
||||
else
|
||||
@@ -800,7 +802,9 @@
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -816,6 +820,26 @@
|
||||
// hintmask and cntrmask
|
||||
if (b == 19 || b == 20)
|
||||
{
|
||||
var minimumFullBytes = CalculatePrecedingHintBytes(precedingCommands, localSubroutines, globalSubroutines);
|
||||
// Skip the following hintmask or cntrmask data bytes
|
||||
i += minimumFullBytes;
|
||||
}
|
||||
|
||||
if (SingleByteCommandStore.TryGetValue(b, out var command))
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -825,10 +849,13 @@
|
||||
var stemCount = 0;
|
||||
var precedingNumbers = 0;
|
||||
var hasEncounteredInitialHintMask = false;
|
||||
for (var j = 0; j < precedingCommands.Count; j++)
|
||||
for (var j = 0; j < commandsToCountHints.Count; j++)
|
||||
{
|
||||
var item = precedingCommands[j];
|
||||
item.Match(x => precedingNumbers++,
|
||||
var item = commandsToCountHints[j];
|
||||
item.Match(x =>
|
||||
{
|
||||
precedingNumbers++;
|
||||
},
|
||||
x =>
|
||||
{
|
||||
// The numbers preceding the first hintmask following hinting can act as vertical hints.
|
||||
@@ -857,17 +884,78 @@
|
||||
fullStemCount += precedingNumbers / 2;
|
||||
}
|
||||
|
||||
var minimumFullBytes = Math.Ceiling(fullStemCount / 8d);
|
||||
// Skip the following hintmask or cntrmask data bytes
|
||||
i += (int)minimumFullBytes;
|
||||
var minimumFullBytes = (int)Math.Ceiling(fullStemCount / 8d);
|
||||
|
||||
return minimumFullBytes;
|
||||
}
|
||||
|
||||
if (SingleByteCommandStore.TryGetValue(b, out var command))
|
||||
private static IReadOnlyList<Union<decimal, LazyType2Command>> BuildFullCommandSequence(IReadOnlyList<Union<decimal, LazyType2Command>> allNonSubroutineCommands,
|
||||
Dictionary<int, Type2CharStrings.CommandSequence> localSubroutines,
|
||||
Dictionary<int, Type2CharStrings.CommandSequence> globalSubroutines)
|
||||
{
|
||||
return command;
|
||||
/*
|
||||
* 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";
|
||||
}
|
||||
|
||||
return new LazyType2Command($"unknown: {b}", x => { });
|
||||
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,10 +48,17 @@
|
||||
throw new InvalidOperationException($"No charstring sequence with the name /{name} in this font.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
glyph = Run(sequence);
|
||||
|
||||
glyphs[name] = glyph;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to interpret charstring for symbol with name: {name}. Commands: {sequence}.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return glyph;
|
||||
}
|
||||
|
@@ -11,24 +11,24 @@
|
||||
internal class CompactFontFormatFont
|
||||
{
|
||||
public CompactFontFormatTopLevelDictionary TopDictionary { get; }
|
||||
private readonly CompactFontFormatPrivateDictionary privateDictionary;
|
||||
private readonly ICompactFontFormatCharset charset;
|
||||
private readonly Union<Type1CharStrings, Type2CharStrings> charStrings;
|
||||
public CompactFontFormatPrivateDictionary PrivateDictionary { get; }
|
||||
public ICompactFontFormatCharset Charset { get; }
|
||||
public Union<Type1CharStrings, Type2CharStrings> CharStrings { get; }
|
||||
|
||||
public CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary, CompactFontFormatPrivateDictionary privateDictionary,
|
||||
ICompactFontFormatCharset charset,
|
||||
Union<Type1CharStrings, Type2CharStrings> charStrings)
|
||||
{
|
||||
TopDictionary = topDictionary;
|
||||
this.privateDictionary = privateDictionary;
|
||||
this.charset = charset;
|
||||
this.charStrings = charStrings;
|
||||
PrivateDictionary = privateDictionary;
|
||||
Charset = charset;
|
||||
CharStrings = charStrings;
|
||||
}
|
||||
|
||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||
{
|
||||
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(); });
|
||||
|
||||
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}.");
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
@@ -19,7 +19,7 @@
|
||||
this.indexReader = indexReader;
|
||||
}
|
||||
|
||||
public CompactFontFormatFontSet Parse(CompactFontFormatData data)
|
||||
public CompactFontFormatFontProgram Parse(CompactFontFormatData data)
|
||||
{
|
||||
var tag = ReadTag(data);
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
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)
|
||||
|
@@ -93,7 +93,7 @@
|
||||
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)
|
||||
{
|
||||
@@ -119,7 +119,7 @@
|
||||
&& NameToken.Type1C.Equals(subTypeName))
|
||||
{
|
||||
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);
|
||||
@@ -127,7 +127,7 @@
|
||||
|
||||
var font = type1FontParser.Parse(new ByteArrayInputBytes(bytes), length1.Int, length2.Int);
|
||||
|
||||
return Union<Type1FontProgram, CompactFontFormatFontSet>.One(font);
|
||||
return Union<Type1FontProgram, CompactFontFormatFontProgram>.One(font);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@@ -29,7 +29,7 @@
|
||||
private readonly Encoding encoding;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly Union<Type1FontProgram, CompactFontFormatFontSet> fontProgram;
|
||||
private readonly Union<Type1FontProgram, CompactFontFormatFontProgram> fontProgram;
|
||||
|
||||
private readonly ToUnicodeCMap toUnicodeCMap;
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
public Type1FontSimple(NameToken name, int firstChar, int lastChar, decimal[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
||||
CMap toUnicodeCMap,
|
||||
Union<Type1FontProgram, CompactFontFormatFontSet> fontProgram)
|
||||
Union<Type1FontProgram, CompactFontFormatFontProgram> fontProgram)
|
||||
{
|
||||
this.firstChar = firstChar;
|
||||
this.lastChar = lastChar;
|
||||
|
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace UglyToad.PdfPig.Util
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
internal abstract class Union<A, B>
|
||||
{
|
||||
public abstract void Match(Action<A> first, Action<B> second);
|
||||
@@ -28,6 +29,7 @@ namespace UglyToad.PdfPig.Util
|
||||
Item = item;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public override void Match(Action<A> first, Action<B> second)
|
||||
{
|
||||
first(Item);
|
||||
@@ -48,6 +50,7 @@ namespace UglyToad.PdfPig.Util
|
||||
Item = item;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public override void Match(Action<A> first, Action<B> second)
|
||||
{
|
||||
second(Item);
|
||||
|
Reference in New Issue
Block a user