mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 11:44:51 +08:00
tidy up cff font classes and start adding type 2 charstring support for #6
This commit is contained in:
78
src/UglyToad.PdfPig/Fonts/CharStringStack.cs
Normal file
78
src/UglyToad.PdfPig/Fonts/CharStringStack.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace UglyToad.PdfPig.Fonts
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// The stack of numeric operands currently active in a CharString.
|
||||
/// </summary>
|
||||
internal class CharStringStack
|
||||
{
|
||||
private readonly List<decimal> stack = new List<decimal>();
|
||||
|
||||
/// <summary>
|
||||
/// The current size of the stack.
|
||||
/// </summary>
|
||||
public int Length => stack.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Whether it's possible to pop a value from either end of the stack.
|
||||
/// </summary>
|
||||
public bool CanPop => stack.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Remove and return the value from the top of the stack.
|
||||
/// </summary>
|
||||
/// <returns>The value from the top of the stack.</returns>
|
||||
public decimal PopTop()
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot pop from the top of an empty stack, invalid charstring parsed.");
|
||||
}
|
||||
|
||||
var result = stack[stack.Count - 1];
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove and return the value from the bottom of the stack.
|
||||
/// </summary>
|
||||
/// <returns>The value from the bottom of the stack.</returns>
|
||||
public decimal PopBottom()
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot pop from the bottom of an empty stack, invalid charstring parsed.");
|
||||
}
|
||||
|
||||
var result = stack[0];
|
||||
stack.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the value to the top of the stack.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to add.</param>
|
||||
public void Push(decimal value)
|
||||
{
|
||||
stack.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all values from the stack.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
stack.Clear();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(" ", stack.Select(x => x.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the deferred execution of a Type 2 CharString command.
|
||||
/// </summary>
|
||||
internal class LazyType2Command
|
||||
{
|
||||
private readonly Action<Type2BuildCharContext> runCommand;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public LazyType2Command(string name, Action<Type2BuildCharContext> runCommand)
|
||||
{
|
||||
Name = name;
|
||||
this.runCommand = runCommand ?? throw new ArgumentNullException(nameof(runCommand));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public void Run(Type2BuildCharContext context)
|
||||
{
|
||||
runCommand(context);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Type2BuildCharContext
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Util;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A Type 2 charstring program is a sequence of unsigned 8-bit bytes that encode numbers and operators.
|
||||
/// The byte value specifies a operator, a number, or subsequent bytes that are to be interpreted in a specific manner
|
||||
/// </remarks>
|
||||
internal class Type2CharStringParser
|
||||
{
|
||||
public static void Parse(IReadOnlyList<IReadOnlyList<byte>> charStringBytes)
|
||||
{
|
||||
for (var i = 0; i < charStringBytes.Count; i++)
|
||||
{
|
||||
var charString = charStringBytes[i];
|
||||
ParseSingle(charString);
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Union<decimal, LazyType2Command>> ParseSingle(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
var instructions = new List<Union<decimal, LazyType2Command>>();
|
||||
for (var i = 0; i < bytes.Count; i++)
|
||||
{
|
||||
var b = bytes[i];
|
||||
if (b <= 31 && b != 28)
|
||||
{
|
||||
var command = GetCommand(b, bytes, ref i);
|
||||
instructions.Add(Union<decimal, LazyType2Command>.Two(command));
|
||||
}
|
||||
else
|
||||
{
|
||||
var number = InterpretNumber(b, bytes, ref i);
|
||||
instructions.Add(Union<decimal, LazyType2Command>.One(number));
|
||||
}
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Type 2 interpretation of a number with an initial byte value of 255 differs from how it is interpreted in the Type 1 format
|
||||
/// and 28 has a special meaning.
|
||||
/// </summary>
|
||||
private static decimal InterpretNumber(byte b, IReadOnlyList<byte> bytes, ref int i)
|
||||
{
|
||||
if (b == 28)
|
||||
{
|
||||
return bytes[++i] << 8 | bytes[++i];
|
||||
}
|
||||
|
||||
if (b >= 32 && b <= 246)
|
||||
{
|
||||
return b - 139;
|
||||
}
|
||||
|
||||
if (b >= 247 && b <= 250)
|
||||
{
|
||||
var w = bytes[++i];
|
||||
return ((b - 247) * 256) + w + 108;
|
||||
}
|
||||
|
||||
if (b >= 251 && b <= 254)
|
||||
{
|
||||
var w = bytes[++i];
|
||||
return -((b - 251) * 256) - w - 108;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the charstring byte contains the value 255, the next four bytes indicate a two's complement signed number.
|
||||
* The first of these the four bytes contains the highest order bits, the second byte contains the next higher order bits
|
||||
* and the fourth byte contains the lowest order bits.
|
||||
* This number is interpreted as a Fixed; that is, a signed number with 16 bits of fraction
|
||||
*/
|
||||
var lead = bytes[++i] << 8 | bytes[++i];
|
||||
var fractionalPart = bytes[++i] << 8 | bytes[++i];
|
||||
|
||||
return lead + (fractionalPart / 65535m);
|
||||
}
|
||||
|
||||
private static readonly IReadOnlyDictionary<int, LazyType2Command> SingleByteCommandStore = new Dictionary<int, LazyType2Command>
|
||||
{
|
||||
{ 1, new LazyType2Command("hstem", x => { })},
|
||||
{ 3, new LazyType2Command("vstem", x => { })},
|
||||
{ 4, new LazyType2Command("vmoveto", x => { })},
|
||||
{ 5, new LazyType2Command("rlineto", x => { })},
|
||||
{ 6, new LazyType2Command("hlineto", x => { })},
|
||||
{ 7, new LazyType2Command("vlineto", x => { })},
|
||||
{ 8, new LazyType2Command("rrcurveto", x => { })},
|
||||
{ 10, new LazyType2Command("callsubr", x => { })},
|
||||
{ 11, new LazyType2Command("return", x => { })},
|
||||
{ 14, new LazyType2Command("endchar", x => { })},
|
||||
{ 18, new LazyType2Command("hstemhm", x => { })},
|
||||
{ 19, new LazyType2Command("hintmask", x => { })},
|
||||
{ 20, new LazyType2Command("cntrmask", x => { })},
|
||||
{ 21, new LazyType2Command("rmoveto", x => { })},
|
||||
{ 22, new LazyType2Command("hmoveto", x => { })},
|
||||
{ 23, new LazyType2Command("vstemhm", x => { })},
|
||||
{ 24, new LazyType2Command("rcurveline", x => { })},
|
||||
{ 25, new LazyType2Command("rlinecurve", x => { })},
|
||||
{ 26, new LazyType2Command("vvcurveto", x => { })},
|
||||
{ 27, new LazyType2Command("hhcurveto", x => { })},
|
||||
{ 29, new LazyType2Command("callgsubr", x => { })},
|
||||
{ 30, new LazyType2Command("vhcurveto", x => { })},
|
||||
{ 31, new LazyType2Command("hvcurveto", x => { })}
|
||||
};
|
||||
|
||||
private static readonly IReadOnlyDictionary<int, LazyType2Command> TwoByteCommandStore = new Dictionary<int, LazyType2Command>
|
||||
{
|
||||
{ 3, new LazyType2Command("and", x => { })},
|
||||
{ 4, new LazyType2Command("or", x => { })},
|
||||
{ 5, new LazyType2Command("not", x => { })},
|
||||
{ 9, new LazyType2Command("abs", x => { })},
|
||||
{ 10, new LazyType2Command("add", x => { })},
|
||||
{ 11, new LazyType2Command("sub", x => { })},
|
||||
{ 12, new LazyType2Command("div", x => { })},
|
||||
{ 14, new LazyType2Command("neg", x => { })},
|
||||
{ 15, new LazyType2Command("eq", x => { })},
|
||||
{ 18, new LazyType2Command("drop", x => { })},
|
||||
{ 20, new LazyType2Command("put", x => { })},
|
||||
{ 21, new LazyType2Command("get", x => { })},
|
||||
{ 22, new LazyType2Command("ifelse", x => { })},
|
||||
{ 23, new LazyType2Command("random", x => { })},
|
||||
{ 24, new LazyType2Command("mul", x => { })},
|
||||
{ 26, new LazyType2Command("sqrt", x => { })},
|
||||
{ 27, new LazyType2Command("dup", x => { })},
|
||||
{ 28, new LazyType2Command("exch", x => { })},
|
||||
{ 29, new LazyType2Command("index", x => { })},
|
||||
{ 30, new LazyType2Command("roll", x => { })},
|
||||
{ 34, new LazyType2Command("hflex", x => { })},
|
||||
{ 35, new LazyType2Command("flex", x => { })},
|
||||
{ 36, new LazyType2Command("hflex1", x => { })},
|
||||
{ 37, new LazyType2Command("flex1", x => { })},
|
||||
};
|
||||
|
||||
private static LazyType2Command GetCommand(byte b, IReadOnlyList<byte> bytes, ref int i)
|
||||
{
|
||||
if (b == 12)
|
||||
{
|
||||
var b2 = bytes[++i];
|
||||
if (TwoByteCommandStore.TryGetValue(b2, out var commandTwoByte))
|
||||
{
|
||||
return commandTwoByte;
|
||||
}
|
||||
|
||||
return new LazyType2Command($"unknown: {b} {b2}", x => {});
|
||||
}
|
||||
|
||||
if (SingleByteCommandStore.TryGetValue(b, out var command))
|
||||
{
|
||||
return command;
|
||||
}
|
||||
|
||||
return new LazyType2Command($"unknown: {b}", x => {});
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the raw bytes of this Compact Font Format file with utility methods for reading data types from it.
|
||||
/// </summary>
|
||||
internal class CompactFontFormatData
|
||||
{
|
||||
private readonly byte[] dataBytes;
|
||||
|
||||
public int Position { get; private set; } = -1;
|
||||
|
||||
public CompactFontFormatData(byte[] dataBytes)
|
||||
{
|
||||
this.dataBytes = dataBytes;
|
||||
}
|
||||
|
||||
public string ReadString(int length, Encoding encoding)
|
||||
{
|
||||
var bytes = new byte[length];
|
||||
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = ReadByte();
|
||||
}
|
||||
|
||||
return encoding.GetString(bytes);
|
||||
}
|
||||
|
||||
public byte ReadCard8()
|
||||
{
|
||||
return ReadByte();
|
||||
}
|
||||
|
||||
public ushort ReadCard16()
|
||||
{
|
||||
return (ushort)(ReadByte() << 8 | ReadByte());
|
||||
}
|
||||
|
||||
public byte ReadOffsize()
|
||||
{
|
||||
return ReadByte();
|
||||
}
|
||||
|
||||
public int ReadOffset(int offsetSize)
|
||||
{
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < offsetSize; i++)
|
||||
{
|
||||
value = value << 8 | ReadByte();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
Position++;
|
||||
|
||||
if (Position >= dataBytes.Length)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot read byte at position {Position} of an array which is {dataBytes.Length} bytes long.");
|
||||
}
|
||||
|
||||
return dataBytes[Position];
|
||||
}
|
||||
|
||||
public byte Peek()
|
||||
{
|
||||
return dataBytes[Position + 1];
|
||||
}
|
||||
|
||||
public bool CanRead()
|
||||
{
|
||||
return Position < dataBytes.Length - 1;
|
||||
}
|
||||
|
||||
public void Seek(int offset)
|
||||
{
|
||||
Position = offset - 1;
|
||||
}
|
||||
|
||||
public long ReadLong()
|
||||
{
|
||||
return (ReadCard16() << 16) | ReadCard16();
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int length)
|
||||
{
|
||||
var result = new byte[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = ReadByte();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// The header table for the binary data of a Compact Font Format file.
|
||||
/// </summary>
|
||||
internal struct CompactFontFormatHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// The major version of this font format. Starting at 1.
|
||||
/// </summary>
|
||||
public byte MajorVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The minor version of this font format. Starting at 0. Indicates extensions to the format which
|
||||
/// are undetectable by readers which do not support them.
|
||||
/// </summary>
|
||||
public byte MinorVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the size of this header in bytes so that future changes to the format may include extra data after the <see cref="OffsetSize"/> field.
|
||||
/// </summary>
|
||||
public byte SizeInBytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the size of all offsets relative to the start of the data in the font.
|
||||
/// </summary>
|
||||
public byte OffsetSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="CompactFontFormatHeader"/>.
|
||||
/// </summary>
|
||||
/// <param name="majorVersion">The major version of this font format.</param>
|
||||
/// <param name="minorVersion">The minor version of this font format.</param>
|
||||
/// <param name="sizeInBytes">Indicates the size of this header in bytes so that future changes to the format may include extra data after the offsetSize field.</param>
|
||||
/// <param name="offsetSize">Specifies the size of all offsets relative to the start of the data in the font.</param>
|
||||
public CompactFontFormatHeader(byte majorVersion, byte minorVersion, byte sizeInBytes, byte offsetSize)
|
||||
{
|
||||
MajorVersion = majorVersion;
|
||||
MinorVersion = minorVersion;
|
||||
SizeInBytes = sizeInBytes;
|
||||
OffsetSize = offsetSize;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Major: {MajorVersion}, Minor: {MinorVersion}, Header Size: {SizeInBytes}, Offset: {OffsetSize}";
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||
{
|
||||
using System;
|
||||
using CharStrings;
|
||||
using Dictionaries;
|
||||
|
||||
internal class CompactFontFormatIndividualFontParser
|
||||
@@ -42,6 +44,17 @@
|
||||
data.Seek(dictionary.CharStringsOffset);
|
||||
|
||||
var index = indexReader.ReadDictionaryData(data);
|
||||
|
||||
switch (dictionary.CharStringType)
|
||||
{
|
||||
case CompactFontFormatCharStringType.Type1:
|
||||
throw new NotImplementedException();
|
||||
case CompactFontFormatCharStringType.Type2:
|
||||
Type2CharStringParser.Parse(index);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Unexpected CharString type in CFF font: {dictionary.CharStringType}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
using Util;
|
||||
|
||||
internal class CompactFontFormatParser
|
||||
@@ -97,126 +96,4 @@
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal class CompactFontFormatData
|
||||
{
|
||||
private readonly byte[] dataBytes;
|
||||
|
||||
public int Position { get; private set; } = -1;
|
||||
|
||||
public CompactFontFormatData(byte[] dataBytes)
|
||||
{
|
||||
this.dataBytes = dataBytes;
|
||||
}
|
||||
|
||||
public string ReadString(int length, Encoding encoding)
|
||||
{
|
||||
var bytes = new byte[length];
|
||||
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
bytes[i] = ReadByte();
|
||||
}
|
||||
|
||||
return encoding.GetString(bytes);
|
||||
}
|
||||
|
||||
public byte ReadCard8()
|
||||
{
|
||||
return ReadByte();
|
||||
}
|
||||
|
||||
public ushort ReadCard16()
|
||||
{
|
||||
return (ushort)(ReadByte() << 8 | ReadByte());
|
||||
}
|
||||
|
||||
public byte ReadOffsize()
|
||||
{
|
||||
return ReadByte();
|
||||
}
|
||||
|
||||
public int ReadOffset(int offsetSize)
|
||||
{
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < offsetSize; i++)
|
||||
{
|
||||
value = value << 8 | ReadByte();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
Position++;
|
||||
|
||||
if (Position >= dataBytes.Length)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"Cannot read byte at position {Position} of an array which is {dataBytes.Length} bytes long.");
|
||||
}
|
||||
|
||||
return dataBytes[Position];
|
||||
}
|
||||
|
||||
public byte Peek()
|
||||
{
|
||||
return dataBytes[Position + 1];
|
||||
}
|
||||
|
||||
public bool CanRead()
|
||||
{
|
||||
return Position < dataBytes.Length - 1;
|
||||
}
|
||||
|
||||
public void Seek(int offset)
|
||||
{
|
||||
Position = offset - 1;
|
||||
}
|
||||
|
||||
public long ReadLong()
|
||||
{
|
||||
return (ReadCard16() << 16) | ReadCard16();
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int length)
|
||||
{
|
||||
var result = new byte[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = ReadByte();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The header table for the binary data of a CFF file.
|
||||
/// </summary>
|
||||
internal struct CompactFontFormatHeader
|
||||
{
|
||||
public byte MajorVersion { get; }
|
||||
|
||||
public byte MinorVersion { get; }
|
||||
|
||||
public byte SizeInBytes { get; }
|
||||
|
||||
public byte OffsetSize { get; }
|
||||
|
||||
public CompactFontFormatHeader(byte majorVersion, byte minorVersion, byte sizeInBytes, byte offsetSize)
|
||||
{
|
||||
MajorVersion = majorVersion;
|
||||
MinorVersion = minorVersion;
|
||||
SizeInBytes = sizeInBytes;
|
||||
OffsetSize = offsetSize;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Major: {MajorVersion}, Minor: {MinorVersion}, Header Size: {SizeInBytes}, Offset: {OffsetSize}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
public decimal PaintType { get; set; }
|
||||
|
||||
public int CharstringType { get; set; } = 2;
|
||||
public CompactFontFormatCharStringType CharStringType { get; set; } = CompactFontFormatCharStringType.Type2;
|
||||
|
||||
public TransformationMatrix FontMatrix { get; set; } = TransformationMatrix.FromValues(0.001m, 0m, 0.001m, 0, 0, 0);
|
||||
|
||||
@@ -62,5 +62,22 @@
|
||||
public string BaseFontName { get; set; }
|
||||
|
||||
public decimal[] BaseFontBlend { get; set; }
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the format of the CharString data contained within a Compact Font Format font.
|
||||
/// </summary>
|
||||
internal enum CompactFontFormatCharStringType
|
||||
{
|
||||
/// <summary>
|
||||
/// The Type 1 CharString format as defined by the Adobe Type 1 Font Format.
|
||||
/// </summary>
|
||||
Type1 = 1,
|
||||
/// <summary>
|
||||
/// The Type 2 CharString format as defined by Adobe Technical Note #5177. This is the default type.
|
||||
/// </summary>
|
||||
Type2 = 2
|
||||
}
|
||||
}
|
@@ -65,7 +65,7 @@
|
||||
dictionary.PaintType = operands[0].Decimal;
|
||||
break;
|
||||
case 6:
|
||||
dictionary.CharstringType = GetIntOrDefault(operands);
|
||||
dictionary.CharStringType = (CompactFontFormatCharStringType)GetIntOrDefault(operands, 2);
|
||||
break;
|
||||
case 7:
|
||||
{
|
||||
|
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the deferred execution of a Type 1 Build Char command.
|
||||
@@ -19,6 +18,7 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
|
||||
this.runCommand = runCommand ?? throw new ArgumentNullException(nameof(runCommand));
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public void Run(Type1BuildCharContext context)
|
||||
{
|
||||
runCommand(context);
|
||||
@@ -29,43 +29,4 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Type1Stack
|
||||
{
|
||||
private readonly List<decimal> stack = new List<decimal>();
|
||||
|
||||
public decimal PopTop()
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot pop from the top of an empty stack, invalid charstring parsed.");
|
||||
}
|
||||
|
||||
var result = stack[stack.Count - 1];
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
public decimal PopBottom()
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot pop from the bottom of an empty stack, invalid charstring parsed.");
|
||||
}
|
||||
|
||||
var result = stack[0];
|
||||
stack.RemoveAt(0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Push(decimal value)
|
||||
{
|
||||
stack.Add(value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
stack.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,9 +19,9 @@
|
||||
|
||||
public PdfPoint CurrentPosition { get; set; }
|
||||
|
||||
public Type1Stack Stack { get; } = new Type1Stack();
|
||||
public CharStringStack Stack { get; } = new CharStringStack();
|
||||
|
||||
public Type1Stack PostscriptStack { get; } = new Type1Stack();
|
||||
public CharStringStack PostscriptStack { get; } = new CharStringStack();
|
||||
|
||||
public IReadOnlyList<PdfPoint> FlexPoints { get; }
|
||||
|
||||
|
Reference in New Issue
Block a user