change type1 commands to be static and lazily evaluated and return the command sequences from the parser

This commit is contained in:
Eliot Jones
2018-11-01 19:34:22 +00:00
parent 61e2a0814d
commit 1d4dc7767d
32 changed files with 515 additions and 541 deletions

View File

@@ -7,48 +7,27 @@
/// the PostScript language procedure at the other subroutine index in the OtherSubrs array in the Private dictionary
/// (or a built-in function equivalent to this procedure) is executed.
/// </summary>
internal class CallOtherSubrCommand
internal static class CallOtherSubrCommand
{
public const string Name = "callothersubr";
public static readonly byte First = 12;
public static readonly byte? Second = 16;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = false;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The arguments to pass to the other subroutine.
/// </summary>
public IReadOnlyList<int> Arguments { get; }
/// <summary>
/// The number of arguments to pass to the other subroutine.
/// </summary>
public int ArgumentCount { get; }
/// <summary>
/// The index of the other subroutine to call in the font's Private dictionary.
/// </summary>
public int OtherSubroutineIndex { get; }
/// <summary>
/// Create a new <see cref="CallOtherSubrCommand"/>.
/// </summary>
/// <param name="arguments">The arguments to pass to the other subroutine.</param>
/// <param name="argumentCount">The number of arguments to pass to the other subroutine.</param>
/// <param name="otherSubroutineIndex">The index of the other subroutine to call in the font's Private dictionary.</param>
public CallOtherSubrCommand(IReadOnlyList<int> arguments, int argumentCount, int otherSubroutineIndex)
public static void Run(Type1BuildCharContext context)
{
Arguments = arguments;
ArgumentCount = argumentCount;
OtherSubroutineIndex = otherSubroutineIndex;
}
public override string ToString()
{
var joined = string.Join(" ", Arguments);
return $"{joined} {ArgumentCount} {OtherSubroutineIndex} {Name}";
var index = (int)context.Stack.PopTop();
var numberOfArguments = (int)context.Stack.PopTop();
var otherSubroutineArguments = new List<decimal>(numberOfArguments);
for (int j = 0; j < numberOfArguments; j++)
{
otherSubroutineArguments.Add(context.Stack.PopTop());
}
}
}
}

View File

@@ -3,33 +3,21 @@
/// <summary>
/// Calls a subroutine with index from the subroutines array in the Private dictionary.
/// </summary>
internal class CallSubrCommand
internal static class CallSubrCommand
{
public const string Name = "callsubr";
public static readonly byte First = 10;
public static readonly byte? Second = null;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = false;
/// <summary>
/// The index of the subroutine in the Private dictionary.
/// </summary>
public int SubroutineIndex { get; }
/// <summary>
/// Creates a new <see cref="CallSubrCommand"/>.
/// </summary>
/// <param name="subroutineIndex">The index of the subroutine in the Private dictionary.</param>
public CallSubrCommand(int subroutineIndex)
public static void Run(Type1BuildCharContext context)
{
SubroutineIndex = subroutineIndex;
}
public override string ToString()
{
return $"{SubroutineIndex} {Name}";
var index = (int)context.Stack.PopTop();
}
}
}

View File

@@ -3,25 +3,26 @@
/// <summary>
/// This operator returns the result of dividing num1 by num2. The result is always a real.
/// </summary>
internal class DivCommand
internal static class DivCommand
{
public const string Name = "div";
public static readonly byte First = 12;
public static readonly byte? Second = 12;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = false;
public static DivCommand Instance { get; } = new DivCommand();
private DivCommand()
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static void Run(Type1BuildCharContext context)
{
}
var first = context.Stack.PopTop();
var second = context.Stack.PopTop();
public override string ToString()
{
return Name;
var result = first / second;
context.Stack.Push(result);
}
}
}

View File

@@ -1,28 +1,25 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
/// <summary>
/// Pops a number from the top of the interpreter operand stack and pushes that number onto the operand stack.
/// Pops a number from the top of the PostScript interpreter operand stack and pushes that number onto the operand stack.
/// This command is used only to retrieve a result from an OtherSubrs procedure.
/// </summary>
internal class PopCommand
internal static class PopCommand
{
public const string Name = "pop";
public static readonly byte First = 12;
public static readonly byte? Second = 17;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static PopCommand Instance { get; } = new PopCommand();
private PopCommand()
public static void Run(Type1BuildCharContext context)
{
}
public override string ToString()
{
return Name;
var num = context.PostscriptStack.PopTop();
context.Stack.Push(num);
}
}
}

View File

@@ -3,25 +3,21 @@
/// <summary>
/// Returns from a charstring subroutine and continues execution in the calling charstring.
/// </summary>
internal class ReturnCommand
internal static class ReturnCommand
{
public const string Name = "return";
public static readonly byte First = 11;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = false;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static ReturnCommand Instance { get; } = new ReturnCommand();
private ReturnCommand()
public static void Run(Type1BuildCharContext context)
{
}
public override string ToString()
{
return Name;
}
}
}

View File

@@ -3,40 +3,24 @@
/// <summary>
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
/// </summary>
internal class SetCurrentPointCommand
internal static class SetCurrentPointCommand
{
public const string Name = "setcurrentpoint";
public static readonly byte First = 12;
public static readonly byte? Second = 33;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The X coordinate in character space.
/// </summary>
public int X { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The Y coordinate in character space.
/// </summary>
public int Y { get; }
/// <summary>
/// Creates a new <see cref="SetCurrentPointCommand"/>.
/// </summary>
/// <param name="x">The X coordinate in character space.</param>
/// <param name="y">The Y coordinate in character space.</param>
public SetCurrentPointCommand(int x, int y)
public static void Run(Type1BuildCharContext context)
{
X = x;
Y = y;
}
var x = context.Stack.PopBottom();
var y = context.Stack.PopBottom();
public override string ToString()
{
return $"{X} {Y} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -3,25 +3,21 @@
/// <summary>
/// Brackets an outline section for the dots in letters such as "i", "j" and "!".
/// </summary>
internal class DotSectionCommand
internal static class DotSectionCommand
{
public const string Name = "dotsection";
public static readonly byte First = 12;
public static readonly byte? Second = 0;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = true;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static DotSectionCommand Instance { get; } = new DotSectionCommand();
private DotSectionCommand()
public static void Run(Type1BuildCharContext context)
{
}
public override string ToString()
{
return Name;
context.Stack.Clear();
}
}
}

View File

@@ -10,41 +10,28 @@
/// <remarks>
/// Suited to letters with 3 horizontal stems like 'E'.
/// </remarks>
internal class HStem3Command
internal static class HStem3Command
{
public const string Name = "hstem3";
public static readonly byte First = 12;
public static readonly byte? Second = 2;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int Y0 { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int DeltaY0 { get; }
public int Y1 { get; }
public int DeltaY1 { get; }
public int Y2 { get; }
public int DeltaY2 { get; }
public HStem3Command(int y0, int deltaY0, int y1, int deltaY1, int y2, int deltaY2)
public static void Run(Type1BuildCharContext context)
{
Y0 = y0;
DeltaY0 = deltaY0;
Y1 = y1;
DeltaY1 = deltaY1;
Y2 = y2;
DeltaY2 = deltaY2;
}
var y0 = context.Stack.PopBottom();
var dy0 = context.Stack.PopBottom();
var y1 = context.Stack.PopBottom();
var dy1 = context.Stack.PopBottom();
var y2 = context.Stack.PopBottom();
var dy2 = context.Stack.PopBottom();
public override string ToString()
{
return $"{Y0} {DeltaY0} {Y1} {DeltaY1} {Y2} {DeltaY2} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -4,40 +4,24 @@
/// Declares the vertical range of a horizontal stem zone between the y coordinates y and y+dy,
/// where y is relative to the y coordinate of the left sidebearing point.
/// </summary>
internal class HStemCommand
internal static class HStemCommand
{
public const string Name = "hstem";
public static readonly byte First = 1;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The first Y coordinate for the stem zone, relative to the current left sidebearing Y point.
/// </summary>
public int Y { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The distance to move from Y vertically for the horizontal stem zone.
/// </summary>
public int DeltaY { get; }
/// <summary>
/// Create a new <see cref="HStemCommand"/>.
/// </summary>
/// <param name="y">The lower Y coordinate of the stem zone.</param>
/// <param name="deltaY">The distance to move from Y vertically for the stem zone.</param>
public HStemCommand(int y, int deltaY)
public static void Run(Type1BuildCharContext context)
{
Y = y;
DeltaY = deltaY;
}
var y = context.Stack.PopBottom();
var dy = context.Stack.PopBottom();
public override string ToString()
{
return $"{Y} {DeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -17,34 +17,21 @@
public static readonly byte First = 12;
public static readonly byte? Second = 1;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int X0 { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int DeltaX0 { get; }
public int X1 { get; }
public int DeltaX1 { get; }
public int X2 { get; }
public int DeltaX2 { get; }
public VStem3Command(int x0, int deltaX0, int x1, int deltaX1, int x2, int deltaX2)
public static void Run(Type1BuildCharContext context)
{
X0 = x0;
DeltaX0 = deltaX0;
X1 = x1;
DeltaX1 = deltaX1;
X2 = x2;
DeltaX2 = deltaX2;
}
var x0 = context.Stack.PopBottom();
var dx0 = context.Stack.PopBottom();
var x1 = context.Stack.PopBottom();
var dx1 = context.Stack.PopBottom();
var x2 = context.Stack.PopBottom();
var dx2 = context.Stack.PopBottom();
public override string ToString()
{
return $"{X0} {DeltaX0} {X1} {DeltaX1} {X2} {DeltaX2} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -4,35 +4,24 @@
/// Declares the horizontal range of a vertical stem zone between the x coordinates x and x+dx,
/// where x is relative to the x coordinate of the left sidebearing point.
/// </summary>
internal class VStemCommand
internal static class VStemCommand
{
public const string Name = "vstem";
public static readonly byte First = 3;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The first X coordinate for the stem zone, relative to the current left sidebearing X point.
/// </summary>
public int X { get; }
/// <summary>
/// The distance to move from X horizontally for the vertical stem zone.
/// </summary>
public int DeltaX { get; }
public VStemCommand(int x, int deltaX)
public static void Run(Type1BuildCharContext context)
{
X = x;
DeltaX = deltaX;
}
var x = context.Stack.PopBottom();
var dx = context.Stack.PopBottom();
public override string ToString()
{
return $"{X} {DeltaX} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
{
/// <summary>
/// Represents the deferred execution of a Type 1 Build Char command.
/// </summary>
internal class LazyType1Command
{
private readonly Action<Type1BuildCharContext> runCommand;
public string Name { get; }
public LazyType1Command(string name, Action<Type1BuildCharContext> runCommand)
{
Name = name;
this.runCommand = runCommand ?? throw new ArgumentNullException(nameof(runCommand));
}
public void Run(Type1BuildCharContext context)
{
runCommand(context);
}
public override string ToString()
{
return Name;
}
}
internal class Type1Stack
{
public decimal PopTop()
{
throw new NotImplementedException();
}
public decimal PopBottom()
{
throw new NotImplementedException();
}
public void Push(decimal value)
{
}
public void Clear()
{
}
}
}

View File

@@ -3,25 +3,21 @@
/// <summary>
/// Closes a sub-path. This command does not reposition the current point.
/// </summary>
internal class ClosePathCommand
internal static class ClosePathCommand
{
public const string Name = "closepath";
public static readonly byte First = 9;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = true;
public static ClosePathCommand Instance { get; } = new ClosePathCommand();
private ClosePathCommand()
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static void Run(Type1BuildCharContext context)
{
}
public override string ToString()
{
return Name;
context.Stack.Clear();
}
}
}

View File

@@ -3,33 +3,23 @@
/// <summary>
/// Horizontal line-to command.
/// </summary>
internal class HLineToCommand
internal static class HLineToCommand
{
public const string Name = "hlineto";
public static readonly byte First = 6;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The length of the horizontal line.
/// </summary>
public int DeltaX { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// Create a new <see cref="HLineToCommand"/>.
/// </summary>
/// <param name="deltaX">The length of the horizontal line.</param>
public HLineToCommand(int deltaX)
public static void Run(Type1BuildCharContext context)
{
DeltaX = deltaX;
}
var deltaX = context.Stack.PopBottom();
public override string ToString()
{
return $"{DeltaX} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -3,33 +3,23 @@
/// <summary>
/// Relative move to for horizontal dimension only.
/// </summary>
internal class HMoveToCommand
internal static class HMoveToCommand
{
public const string Name = "hmoveto";
public static readonly byte First = 22;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The distance to move in horizontally.
/// </summary>
public int X { get; set; }
/// <summary>
/// Create a new <see cref="HMoveToCommand"/>.
/// </summary>
/// <param name="x">The distance to move horizontally.</param>
public HMoveToCommand(int x)
{
X = x;
}
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public override string ToString()
public static void Run(Type1BuildCharContext context)
{
return $"{X} {Name}";
var x = context.Stack.PopBottom();
context.Stack.Clear();
}
}
}

View File

@@ -4,38 +4,26 @@
/// Horizontal vertical curve to command. Draws a Bézier curve when the first Bézier tangent is horizontal and the second Bézier tangent is vertical.
/// Equivalent to dx1 0 dx2 dy2 0 dy3 rrcurveto.
/// </summary>
internal class HvCurveToCommand
internal static class HvCurveToCommand
{
public const string Name = "hvcurveto";
public static readonly byte First = 31;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int PostControlPointDeltaX { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int PreControlPointDeltaX { get; }
public int PreControlPointDeltaY { get; }
public int EndPointDeltaY { get; }
/// <summary>
/// Create a new <see cref="HvCurveToCommand"/>.
/// </summary>
public HvCurveToCommand(int postControlPointDeltaX, int preControlPointDeltaX, int preControlPointDeltaY, int endPointDeltaY)
public static void Run(Type1BuildCharContext context)
{
PostControlPointDeltaX = postControlPointDeltaX;
PreControlPointDeltaX = preControlPointDeltaX;
PreControlPointDeltaY = preControlPointDeltaY;
EndPointDeltaY = endPointDeltaY;
}
var postControlPointDeltaX = context.Stack.PopBottom();
var preControlPointDeltaX = context.Stack.PopBottom();
var preControlPointDeltaY = context.Stack.PopBottom();
var endPointDeltaY = context.Stack.PopBottom();
public override string ToString()
{
return $"{PostControlPointDeltaX} {PreControlPointDeltaX} {PreControlPointDeltaY} {EndPointDeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -3,40 +3,24 @@
/// <summary>
/// Relative line-to command. Creates a line moving a distance relative to the current point.
/// </summary>
internal class RLineToCommand
internal static class RLineToCommand
{
public const string Name = "rlineto";
public static readonly byte First = 5;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The distance to move horizontally.
/// </summary>
public int DeltaX { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The distance to move vertically.
/// </summary>
public int DeltaY { get; }
/// <summary>
/// Create a new <see cref="RLineToCommand"/>.
/// </summary>
/// <param name="deltaX">The distance to move horizontally.</param>
/// <param name="deltaY">The distance to move vertically.</param>
public RLineToCommand(int deltaX, int deltaY)
public static void Run(Type1BuildCharContext context)
{
DeltaX = deltaX;
DeltaY = deltaY;
}
var deltaX = context.Stack.PopBottom();
var deltaY = context.Stack.PopBottom();
public override string ToString()
{
return $"{DeltaX} {DeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -9,40 +9,24 @@
/// If the previous path operation in the current path was also a moveto or rmoveto,
/// that point is deleted from the current path and the new moveto point replaces it.
/// </remarks>
internal class RMoveToCommand
internal static class RMoveToCommand
{
public const string Name = "rmoveto";
public static readonly byte First = 21;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The distance to move horizontally.
/// </summary>
public int DeltaX { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The distance to move vertically.
/// </summary>
public int DeltaY { get; }
/// <summary>
/// Create a new <see cref="RMoveToCommand"/>.
/// </summary>
/// <param name="deltaX">The distance to move horizontally.</param>
/// <param name="deltaY">The distance to move vertically.</param>
public RMoveToCommand(int deltaX, int deltaY)
public static void Run(Type1BuildCharContext context)
{
DeltaX = deltaX;
DeltaY = deltaY;
}
var deltaX = context.Stack.PopBottom();
var deltaY = context.Stack.PopBottom();
public override string ToString()
{
return $"{DeltaX} {DeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -5,36 +5,28 @@
/// point, the arguments to rrcurveto are relative to each other.
/// Equivalent to: dx1 dy1 (dx1+dx2) (dy1+dy2) (dx1+dx2+dx3) (dy1+dy2+dy3) rcurveto.
/// </summary>
internal class RelativeRCurveToCommand
internal static class RelativeRCurveToCommand
{
public const string Name = "rrcurveto";
public static readonly byte First = 8;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int DeltaX1 { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int DeltaY1 { get; }
public int DeltaX2 { get; }
public int DeltaY2 { get; }
public int DeltaX3 { get; }
public int DeltaY3 { get; }
public RelativeRCurveToCommand(int deltaX1, int deltaY1, int deltaX2, int deltaY2, int deltaX3, int deltaY3)
public static void Run(Type1BuildCharContext context)
{
DeltaX1 = deltaX1;
DeltaY1 = deltaY1;
DeltaX2 = deltaX2;
DeltaY2 = deltaY2;
DeltaX3 = deltaX3;
DeltaY3 = deltaY3;
var dx1 = context.Stack.PopBottom();
var dy1 = context.Stack.PopBottom();
var dx2 = context.Stack.PopBottom();
var dy2 = context.Stack.PopBottom();
var dx3 = context.Stack.PopBottom();
var dy3 = context.Stack.PopBottom();
context.Stack.Clear();
}
}
}

View File

@@ -3,33 +3,23 @@
/// <summary>
/// Vertical-line to command.
/// </summary>
internal class VLineToCommand
internal static class VLineToCommand
{
public const string Name = "vlineto";
public static readonly byte First = 7;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The length of the vertical line.
/// </summary>
public int DeltaY { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// Create a new <see cref="VLineToCommand"/>.
/// </summary>
/// <param name="deltaY">The length of the vertical line.</param>
public VLineToCommand(int deltaY)
public static void Run(Type1BuildCharContext context)
{
DeltaY = deltaY;
}
var deltaY = context.Stack.PopBottom();
public override string ToString()
{
return $"{DeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -3,33 +3,23 @@
/// <summary>
/// Vertical move to. Moves relative to the current point.
/// </summary>
internal class VMoveToCommand
internal static class VMoveToCommand
{
public const string Name = "vmoveto";
public static readonly byte First = 4;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The distance to move vertically.
/// </summary>
public int DeltaY { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// Create a new <see cref="VMoveToCommand"/>.
/// </summary>
/// <param name="deltaY">The distance to move vertically.</param>
public VMoveToCommand(int deltaY)
public static void Run(Type1BuildCharContext context)
{
DeltaY = deltaY;
}
var deltaY = context.Stack.PopBottom();
public override string ToString()
{
return $"{DeltaY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -5,30 +5,26 @@
/// Equivalent to 0 dy1 dx2 dy2 dx3 0 rrcurveto.
/// This command eliminates two arguments from an rrcurveto call when the first Bézier tangent is vertical and the second Bézier tangent is horizontal.
/// </summary>
internal class VhCurveToCommand
internal static class VhCurveToCommand
{
public const string Name = "vhcurveto";
public static readonly byte First = 30;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int PostControlPointDeltaY { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int PreControlPointDeltaX { get; }
public int PreControlPointDeltaY { get; }
public int EndPointDeltaX { get; }
public VhCurveToCommand(int postControlPointDeltaY, int preControlPointDeltaX, int preControlPointDeltaY, int endPointDeltaX)
public static void Run(Type1BuildCharContext context)
{
PostControlPointDeltaY = postControlPointDeltaY;
PreControlPointDeltaX = preControlPointDeltaX;
PreControlPointDeltaY = preControlPointDeltaY;
EndPointDeltaX = endPointDeltaX;
var postControlPointDeltaY = context.Stack.PopBottom();
var preControlPointDeltaX = context.Stack.PopBottom();
var preControlPointDeltaY = context.Stack.PopBottom();
var endPointDeltaX = context.Stack.PopBottom();
context.Stack.Clear();
}
}
}

View File

@@ -4,25 +4,21 @@
/// Finishes a charstring outline definition and must be the last command in a character's outline
/// (except for accented characters defined using seac).
/// </summary>
internal class EndCharCommand
internal static class EndCharCommand
{
public const string Name = "endchar";
public static readonly byte First = 14;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = false;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = true;
public static EndCharCommand Instance { get; } = new EndCharCommand();
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
private EndCharCommand()
public static void Run(Type1BuildCharContext context)
{
}
public override string ToString()
{
return Name;
context.Stack.Clear();
}
}
}

View File

@@ -6,24 +6,24 @@
/// This command sets the left sidebearing point at (sbx, 0) and sets the character width vector to(wx, 0) in character space.
/// This command also sets the current point to (sbx, 0), but does not place the point in the character path.
/// </summary>
internal class HsbwCommand
internal static class HsbwCommand
{
public const string Name = "hsbw";
public static readonly byte First = 13;
public static readonly byte? Second = null;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
public int LeftSidebearingPointX { get; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public int CharacterWidthVectorX { get; }
public HsbwCommand(int leftSidebearingPointX, int characterWidthVectorX)
public static void Run(Type1BuildCharContext context)
{
LeftSidebearingPointX = leftSidebearingPointX;
CharacterWidthVectorX = characterWidthVectorX;
var leftSidebearingPointX = context.Stack.PopBottom();
var characterWidthVectorX = context.Stack.PopBottom();
context.Stack.Clear();
}
}
}

View File

@@ -11,47 +11,19 @@
public static readonly byte First = 12;
public static readonly byte? Second = 7;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The X value of the left sidebearing point.
/// </summary>
public int LeftSidebearingX { get; set; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The Y value of the left sidebearing point.
/// </summary>
public int LeftSidebearingY { get; set; }
/// <summary>
/// The X value of the character width vector.
/// </summary>
public int CharacterWidthX { get; }
/// <summary>
/// The Y value of the character width vector.
/// </summary>
public int CharacterWidthY { get; }
/// <summary>
/// Create a new <see cref="SbwCommand"/>.
/// </summary>
/// <param name="leftSidebearingX">The X value of the left sidebearing point.</param>
/// <param name="leftSidebearingY">The Y value of the left sidebearing point.</param>
/// <param name="characterWidthX">The X value of the character width vector.</param>
/// <param name="characterWidthY">The Y value of the character width vector.</param>
public SbwCommand(int leftSidebearingX, int leftSidebearingY, int characterWidthX, int characterWidthY)
public static void Run(Type1BuildCharContext context)
{
LeftSidebearingX = leftSidebearingX;
LeftSidebearingY = leftSidebearingY;
CharacterWidthX = characterWidthX;
CharacterWidthY = characterWidthY;
}
var leftSidebearingX = context.Stack.PopBottom();
var leftSidebearingY = context.Stack.PopBottom();
var characterWidthX = context.Stack.PopBottom();
var characterWidthY = context.Stack.PopBottom();
public override string ToString()
{
return $"{LeftSidebearingX} {LeftSidebearingY} {CharacterWidthX} {CharacterWidthY} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -4,61 +4,27 @@
/// Standard encoding accented character.
/// Makes an accented character from two other characters in the font program.
/// </summary>
internal class SeacCommand
internal static class SeacCommand
{
public const string Name = "seac";
public static readonly byte First = 12;
public static readonly byte? Second = 6;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public static bool TakeFromStackBottom { get; } = true;
public static bool ClearsOperandStack { get; } = true;
/// <summary>
/// The x component of the left sidebearing of the accent.
/// </summary>
public int AccentLeftSidebearingX { get; set; }
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
/// <summary>
/// The x position of the origin of the accent character relative to the base character.
/// </summary>
public int AccentOriginX { get; set; }
/// <summary>
/// The y position of the origin of the accent character relative to the base character.
/// </summary>
public int AccentOriginY { get; set; }
/// <summary>
/// The character code of the base character.
/// </summary>
public int BaseCharacterCode { get; set; }
/// <summary>
/// The character code of the accent character.
/// </summary>
public int AccentCharacterCode { get; set; }
/// <summary>
/// Create a new <see cref="SeacCommand"/>.
/// </summary>
/// <param name="accentLeftSidebearingX">The x component of the left sidebearing of the accent.</param>
/// <param name="accentOriginX">The x position of the origin of the accent character relative to the base character.</param>
/// <param name="accentOriginY">The y position of the origin of the accent character relative to the base character.</param>
/// <param name="baseCharacterCode">The character code of the base character.</param>
/// <param name="accentCharacterCode">The character code of the accent character.</param>
public SeacCommand(int accentLeftSidebearingX, int accentOriginX, int accentOriginY, int baseCharacterCode, int accentCharacterCode)
public static void Run(Type1BuildCharContext context)
{
AccentLeftSidebearingX = accentLeftSidebearingX;
AccentOriginX = accentOriginX;
AccentOriginY = accentOriginY;
BaseCharacterCode = baseCharacterCode;
AccentCharacterCode = accentCharacterCode;
}
var accentLeftSidebearingX = context.Stack.PopBottom();
var accentOriginX = context.Stack.PopBottom();
var accentOriginY = context.Stack.PopBottom();
var baseCharacterCode = context.Stack.PopBottom();
var accentCharacterCode = context.Stack.PopBottom();
public override string ToString()
{
return $"{AccentLeftSidebearingX} {AccentOriginX} {AccentOriginY} {BaseCharacterCode} {AccentCharacterCode} {Name}";
context.Stack.Clear();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands
{
internal class Type1BuildCharContext
{
public Type1Stack Stack { get; }
public Type1Stack PostscriptStack { get; }
}
}

View File

@@ -1,30 +0,0 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
using System.Collections.Generic;
using Commands.Hint;
internal static class Type1CharStringCommandFactory
{
public static object GetCommand(List<int> arguments, byte v, IReadOnlyList<byte> bytes, ref int i)
{
switch (v)
{
case 1:
{
return new HStemCommand(arguments[0], arguments[1]);
}
case 12:
{
var next = bytes[++i];
switch (next)
{
}
break;
}
}
return null;
}
}
}

View File

@@ -1,36 +1,99 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
using System;
using System.Collections.Generic;
using Commands;
using Commands.Arithmetic;
using Commands.Hint;
using Commands.PathConstruction;
using Commands.StartFinishOutline;
using Parser;
using Util;
internal class Type1CharStringParser
/// <summary>
/// Decodes a set of CharStrings to their corresponding Type 1 BuildChar operations.
/// </summary>
/// <remarks>
/// A charstring is an encrypted sequence of unsigned 8-bit bytes that encode integers and commands.
/// Type 1 BuildChar, when interpreting a charstring, will first decrypt it and then will decode
/// its bytes one at a time in sequence.
///
/// The value in a byte indicates a command, a number, or subsequent bytes that are to be interpreted
/// in a special way.
///
/// Once the bytes are decoded into numbers and commands, the execution of these numbers and commands proceeds in a
/// manner similar to the operation of the PostScript language. Type 1 BuildChar uses its own operand stack,
/// called the Type 1 BuildChar operand stack, that is distinct from the PostScript interpreter operand stack.
///
/// This stack holds up to 24 numeric entries. A number, decoded from a charstring, is pushed onto the Type 1
/// BuildChar operand stack. A command expects its arguments in order on this operand stack with all arguments generally taken
/// from the bottom of the stack (first argument bottom-most);
/// however, some commands, particularly the subroutine commands, normally work from the top of the stack. If a command returns
/// results, they are pushed onto the Type 1 BuildChar operand stack (last result topmost).
/// </remarks>
internal static class Type1CharStringParser
{
public static void Parse(IReadOnlyList<Type1CharstringDecryptedBytes> charStrings, IReadOnlyList<Type1CharstringDecryptedBytes> subroutines)
public static Type1CharStrings Parse(IReadOnlyList<Type1CharstringDecryptedBytes> charStrings, IReadOnlyList<Type1CharstringDecryptedBytes> subroutines)
{
if (charStrings == null)
{
throw new ArgumentNullException(nameof(charStrings));
}
if (subroutines == null)
{
throw new ArgumentNullException(nameof(subroutines));
}
var charStringResults = new Dictionary<string, Type1CharStrings.CommandSequence>(charStrings.Count);
foreach (var charString in charStrings)
{
ParseSingle(charString.Bytes);
var commandSequence = ParseSingle(charString.Bytes);
charStringResults[charString.Name] = new Type1CharStrings.CommandSequence(commandSequence);
}
var subroutineResults = new Dictionary<int, Type1CharStrings.CommandSequence>(subroutines.Count);
foreach (var subroutine in subroutines)
{
var commandSequence = ParseSingle(subroutine.Bytes);
subroutineResults[subroutine.Index] = new Type1CharStrings.CommandSequence(commandSequence);
}
return new Type1CharStrings(charStringResults, subroutineResults);
}
private static void ParseSingle(IReadOnlyList<byte> charStringBytes)
private static IReadOnlyList<DiscriminatedUnion<decimal, LazyType1Command>> ParseSingle(IReadOnlyList<byte> charStringBytes)
{
var numberStack = new List<int>();
var interpreted = new List<DiscriminatedUnion<decimal, LazyType1Command>>();
for (var i = 0; i < charStringBytes.Count; i++)
{
var b = charStringBytes[i];
if (b <= 31)
{
var command = Type1CharStringCommandFactory.GetCommand(numberStack, b, charStringBytes, ref i);
var command = GetCommand(b, charStringBytes, ref i);
if (command == null)
{
throw new InvalidOperationException($"Could not find command with code {b}.");
}
interpreted.Add(new DiscriminatedUnion<decimal, LazyType1Command>.Case2(command));
}
else
{
var val = InterpretNumber(b, charStringBytes, ref i);
numberStack.Add(val);
interpreted.Add(new DiscriminatedUnion<decimal, LazyType1Command>.Case1(val));
}
}
return interpreted;
}
private static int InterpretNumber(byte b, IReadOnlyList<byte> bytes, ref int i)
@@ -52,9 +115,78 @@
return -((b - 251) * 256) - w - 108;
}
var result = bytes[++i] << 24 + bytes[++i] << 16 + bytes[++i] << 8 + bytes[++i];
var result = bytes[++i] << 24 | bytes[++i] << 16 | bytes[++i] << 8 | bytes[++i];
return result;
}
public static LazyType1Command GetCommand(byte v, IReadOnlyList<byte> bytes, ref int i)
{
switch (v)
{
case 1:
return HStemCommand.Lazy;
case 3:
return VStemCommand.Lazy;
case 4:
return VMoveToCommand.Lazy;
case 5:
return RLineToCommand.Lazy;
case 6:
return HLineToCommand.Lazy;
case 7:
return VLineToCommand.Lazy;
case 8:
return RelativeRCurveToCommand.Lazy;
case 9:
return ClosePathCommand.Lazy;
case 10:
return CallSubrCommand.Lazy;
case 11:
return ReturnCommand.Lazy;
case 13:
return HsbwCommand.Lazy;
case 14:
return EndCharCommand.Lazy;
case 21:
return RMoveToCommand.Lazy;
case 22:
return HMoveToCommand.Lazy;
case 30:
return VhCurveToCommand.Lazy;
case 31:
return HvCurveToCommand.Lazy;
case 12:
{
var next = bytes[++i];
switch (next)
{
case 0:
return DotSectionCommand.Lazy;
case 1:
return VStem3Command.Lazy;
case 2:
return HStem3Command.Lazy;
case 6:
return SeacCommand.Lazy;
case 7:
return SbwCommand.Lazy;
case 12:
return DivCommand.Lazy;
case 16:
return CallOtherSubrCommand.Lazy;
case 17:
return PopCommand.Lazy;
case 33:
return SetCurrentPointCommand.Lazy;
}
break;
}
}
return null;
}
}
}

View File

@@ -0,0 +1,36 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
using System;
using System.Collections.Generic;
using System.Linq;
using Commands;
using Util;
internal class Type1CharStrings
{
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
public IReadOnlyDictionary<int, CommandSequence> Subroutines { get; }
public Type1CharStrings(IReadOnlyDictionary<string, CommandSequence> charStrings, IReadOnlyDictionary<int, CommandSequence> subroutines)
{
CharStrings = charStrings ?? throw new ArgumentNullException(nameof(charStrings));
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
}
public class CommandSequence
{
public IReadOnlyList<DiscriminatedUnion<decimal, LazyType1Command>> Commands { get; }
public CommandSequence(IReadOnlyList<DiscriminatedUnion<decimal, LazyType1Command>> commands)
{
Commands = commands ?? throw new ArgumentNullException(nameof(commands));
}
public override string ToString()
{
return string.Join(", ", Commands.Select(x => x.ToString()));
}
}
}
}

View File

@@ -290,7 +290,7 @@
charStrings = new Type1CharstringDecryptedBytes[0];
}
Type1CharStringParser.Parse(charStrings, builder.Subroutines ?? new Type1CharstringDecryptedBytes[0]);
var instructions = Type1CharStringParser.Parse(charStrings, builder.Subroutines ?? new Type1CharstringDecryptedBytes[0]);
return decrypted;
}

View File

@@ -0,0 +1,52 @@
using System;
namespace UglyToad.PdfPig.Util
{
// ReSharper disable once InconsistentNaming
internal abstract class DiscriminatedUnion<A, B>
{
public abstract T Match<T>(Func<A, T> first, Func<B, T> second);
private DiscriminatedUnion() { }
public sealed class Case1 : DiscriminatedUnion<A, B>
{
public readonly A Item;
public Case1(A item)
{
Item = item;
}
public override T Match<T>(Func<A, T> first, Func<B, T> second)
{
return first(Item);
}
public override string ToString()
{
return Item?.ToString() ?? string.Empty;
}
}
public sealed class Case2 : DiscriminatedUnion<A, B>
{
public readonly B Item;
public Case2(B item)
{
Item = item;
}
public override T Match<T>(Func<A, T> first, Func<B, T> second)
{
return second(Item);
}
public override string ToString()
{
return Item?.ToString() ?? string.Empty;
}
}
}
}