diff --git a/src/UglyToad.PdfPig.Tests/UglyToad.PdfPig.Tests.csproj b/src/UglyToad.PdfPig.Tests/UglyToad.PdfPig.Tests.csproj index 424de5e6..b43d4233 100644 --- a/src/UglyToad.PdfPig.Tests/UglyToad.PdfPig.Tests.csproj +++ b/src/UglyToad.PdfPig.Tests/UglyToad.PdfPig.Tests.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0 diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs new file mode 100644 index 00000000..8ddf1e23 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs @@ -0,0 +1,54 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + using System.Collections.Generic; + + /// + /// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then + /// 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. + /// + internal 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; + + /// + /// The arguments to pass to the other subroutine. + /// + public IReadOnlyList Arguments { get; } + + /// + /// The number of arguments to pass to the other subroutine. + /// + public int ArgumentCount { get; } + + /// + /// The index of the other subroutine to call in the font's Private dictionary. + /// + public int OtherSubroutineIndex { get; } + + /// + /// Create a new . + /// + /// The arguments to pass to the other subroutine. + /// The number of arguments to pass to the other subroutine. + /// The index of the other subroutine to call in the font's Private dictionary. + public CallOtherSubrCommand(IReadOnlyList arguments, int argumentCount, int otherSubroutineIndex) + { + Arguments = arguments; + ArgumentCount = argumentCount; + OtherSubroutineIndex = otherSubroutineIndex; + } + + public override string ToString() + { + var joined = string.Join(" ", Arguments); + return $"{joined} {ArgumentCount} {OtherSubroutineIndex} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallSubrCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallSubrCommand.cs new file mode 100644 index 00000000..a3481fe2 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/CallSubrCommand.cs @@ -0,0 +1,35 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + /// + /// Calls a subroutine with index from the subroutines array in the Private dictionary. + /// + internal class CallSubrCommand + { + public const string Name = "callsubr"; + + public static readonly byte First = 10; + public static readonly byte? Second = null; + + public bool TakeFromStackBottom { get; } = false; + public bool ClearsOperandStack { get; } = false; + + /// + /// The index of the subroutine in the Private dictionary. + /// + public int SubroutineIndex { get; } + + /// + /// Creates a new . + /// + /// The index of the subroutine in the Private dictionary. + public CallSubrCommand(int subroutineIndex) + { + SubroutineIndex = subroutineIndex; + } + + public override string ToString() + { + return $"{SubroutineIndex} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/DivCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/DivCommand.cs new file mode 100644 index 00000000..4c28710a --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/DivCommand.cs @@ -0,0 +1,27 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + /// + /// This operator returns the result of dividing num1 by num2. The result is always a real. + /// + internal 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 override string ToString() + { + return Name; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/PopCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/PopCommand.cs new file mode 100644 index 00000000..4ce36f45 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/PopCommand.cs @@ -0,0 +1,28 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + /// + /// Pops a number from the top of the interpreter operand stack and pushes that number onto the operand stack. + /// This command is used only to retrieve a result from an OtherSubrs procedure. + /// + internal class PopCommand + { + public const string Name = "pop"; + + public static readonly byte First = 12; + public static readonly byte? Second = 17; + + public bool TakeFromStackBottom { get; } = false; + public bool ClearsOperandStack { get; } = false; + + public static PopCommand Instance { get; } = new PopCommand(); + + private PopCommand() + { + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/ReturnCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/ReturnCommand.cs new file mode 100644 index 00000000..e5614a35 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/ReturnCommand.cs @@ -0,0 +1,27 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + /// + /// Returns from a charstring subroutine and continues execution in the calling charstring. + /// + internal 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 ReturnCommand Instance { get; } = new ReturnCommand(); + + private ReturnCommand() + { + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs new file mode 100644 index 00000000..1aac3e2f --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs @@ -0,0 +1,42 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic +{ + /// + /// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command. + /// + internal 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; + + /// + /// The X coordinate in character space. + /// + public int X { get; } + + /// + /// The Y coordinate in character space. + /// + public int Y { get; } + + /// + /// Creates a new . + /// + /// The X coordinate in character space. + /// The Y coordinate in character space. + public SetCurrentPointCommand(int x, int y) + { + X = x; + Y = y; + } + + public override string ToString() + { + return $"{X} {Y} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs new file mode 100644 index 00000000..d99ec410 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs @@ -0,0 +1,6 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint +{ + internal class DotSectionCommand + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs new file mode 100644 index 00000000..c1d989aa --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs @@ -0,0 +1,6 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint +{ + internal class HStem3Command + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs new file mode 100644 index 00000000..b067f838 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs @@ -0,0 +1,6 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint +{ + internal class HStemCommand + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs new file mode 100644 index 00000000..5f7314c7 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs @@ -0,0 +1,6 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint +{ + internal class VStem3Command + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs new file mode 100644 index 00000000..ce119b5e --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs @@ -0,0 +1,6 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint +{ + internal class VStemCommand + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/ClosePathCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/ClosePathCommand.cs new file mode 100644 index 00000000..29087499 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/ClosePathCommand.cs @@ -0,0 +1,27 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Closes a sub-path. This command does not reposition the current point. + /// + internal 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 ClosePathCommand Instance { get; } = new ClosePathCommand(); + + private ClosePathCommand() + { + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HLineToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HLineToCommand.cs new file mode 100644 index 00000000..23cf4afe --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HLineToCommand.cs @@ -0,0 +1,35 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Horizontal line-to command. + /// + internal 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; + + /// + /// The length of the horizontal line. + /// + public int DeltaX { get; } + + /// + /// Create a new . + /// + /// The length of the horizontal line. + public HLineToCommand(int deltaX) + { + DeltaX = deltaX; + } + + public override string ToString() + { + return $"{DeltaX} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs new file mode 100644 index 00000000..c1e935e3 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs @@ -0,0 +1,35 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Relative move to for horizontal dimension only. + /// + internal 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; + + /// + /// The distance to move in horizontally. + /// + public int X { get; set; } + + /// + /// Create a new . + /// + /// The distance to move horizontally. + public HMoveToCommand(int x) + { + X = x; + } + + public override string ToString() + { + return $"{X} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HvCurveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HvCurveToCommand.cs new file mode 100644 index 00000000..03f4d112 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/HvCurveToCommand.cs @@ -0,0 +1,41 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// 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. + /// + internal 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 int PostControlPointDeltaX { get; } + + public int PreControlPointDeltaX { get; } + + public int PreControlPointDeltaY { get; } + + public int EndPointDeltaY { get; } + + /// + /// Create a new . + /// + public HvCurveToCommand(int postControlPointDeltaX, int preControlPointDeltaX, int preControlPointDeltaY, int endPointDeltaY) + { + PostControlPointDeltaX = postControlPointDeltaX; + PreControlPointDeltaX = preControlPointDeltaX; + PreControlPointDeltaY = preControlPointDeltaY; + EndPointDeltaY = endPointDeltaY; + } + + public override string ToString() + { + return $"{PostControlPointDeltaX} {PreControlPointDeltaX} {PreControlPointDeltaY} {EndPointDeltaY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RLineToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RLineToCommand.cs new file mode 100644 index 00000000..c038d248 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RLineToCommand.cs @@ -0,0 +1,42 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Relative line-to command. Creates a line moving a distance relative to the current point. + /// + internal 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; + + /// + /// The distance to move horizontally. + /// + public int DeltaX { get; } + + /// + /// The distance to move vertically. + /// + public int DeltaY { get; } + + /// + /// Create a new . + /// + /// The distance to move horizontally. + /// The distance to move vertically. + public RLineToCommand(int deltaX, int deltaY) + { + DeltaX = deltaX; + DeltaY = deltaY; + } + + public override string ToString() + { + return $"{DeltaX} {DeltaY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs new file mode 100644 index 00000000..78b8be07 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs @@ -0,0 +1,48 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Relative move to command. starts a new subpath of the current path in the same manner as moveto. + /// However, the number pair is interpreted as a displacement relative to the current point (x, y) rather than as an absolute coordinate. + /// + /// + /// moveto: moveto sets the current point in the graphics state to the user space coordinate (x, y) without adding any line segments to the current path. + /// 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. + /// + internal 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; + + /// + /// The distance to move horizontally. + /// + public int DeltaX { get; } + + /// + /// The distance to move vertically. + /// + public int DeltaY { get; } + + /// + /// Create a new . + /// + /// The distance to move horizontally. + /// The distance to move vertically. + public RMoveToCommand(int deltaX, int deltaY) + { + DeltaX = deltaX; + DeltaY = deltaY; + } + + public override string ToString() + { + return $"{DeltaX} {DeltaY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RelativeRCurveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RelativeRCurveToCommand.cs new file mode 100644 index 00000000..92b8dae3 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/RelativeRCurveToCommand.cs @@ -0,0 +1,40 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Relative rcurveto. Whereas the arguments to the rcurveto operator in the PostScript language are all relative to the current + /// 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. + /// + internal 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 int DeltaX1 { get; } + + 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) + { + DeltaX1 = deltaX1; + DeltaY1 = deltaY1; + DeltaX2 = deltaX2; + DeltaY2 = deltaY2; + DeltaX3 = deltaX3; + DeltaY3 = deltaY3; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VLineToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VLineToCommand.cs new file mode 100644 index 00000000..e08ae45a --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VLineToCommand.cs @@ -0,0 +1,35 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Vertical-line to command. + /// + internal 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; + + /// + /// The length of the vertical line. + /// + public int DeltaY { get; } + + /// + /// Create a new . + /// + /// The length of the vertical line. + public VLineToCommand(int deltaY) + { + DeltaY = deltaY; + } + + public override string ToString() + { + return $"{DeltaY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs new file mode 100644 index 00000000..a5c46232 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs @@ -0,0 +1,35 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Vertical move to. Moves relative to the current point. + /// + internal 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; + + /// + /// The distance to move vertically. + /// + public int DeltaY { get; } + + /// + /// Create a new . + /// + /// The distance to move vertically. + public VMoveToCommand(int deltaY) + { + DeltaY = deltaY; + } + + public override string ToString() + { + return $"{DeltaY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VhCurveToCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VhCurveToCommand.cs new file mode 100644 index 00000000..04cd2521 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/PathConstruction/VhCurveToCommand.cs @@ -0,0 +1,34 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction +{ + /// + /// Vertical-horizontal curveto. + /// 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. + /// + internal 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 int PostControlPointDeltaY { get; } + + public int PreControlPointDeltaX { get; } + + public int PreControlPointDeltaY { get; } + + public int EndPointDeltaX { get; } + + public VhCurveToCommand(int postControlPointDeltaY, int preControlPointDeltaX, int preControlPointDeltaY, int endPointDeltaX) + { + PostControlPointDeltaY = postControlPointDeltaY; + PreControlPointDeltaX = preControlPointDeltaX; + PreControlPointDeltaY = preControlPointDeltaY; + EndPointDeltaX = endPointDeltaX; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/EndCharCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/EndCharCommand.cs new file mode 100644 index 00000000..74c76031 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/EndCharCommand.cs @@ -0,0 +1,28 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline +{ + /// + /// Finishes a charstring outline definition and must be the last command in a character's outline + /// (except for accented characters defined using seac). + /// + internal 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 EndCharCommand Instance { get; } = new EndCharCommand(); + + private EndCharCommand() + { + } + + public override string ToString() + { + return Name; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/HsbwCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/HsbwCommand.cs new file mode 100644 index 00000000..9141c061 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/HsbwCommand.cs @@ -0,0 +1,29 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline +{ + /// + /// The name hsbw stands for horizontal sidebearing and width; + /// horizontal indicates that the y component of both the sidebearing and width is 0. + /// 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. + /// + internal 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 int LeftSidebearingPointX { get; } + + public int CharacterWidthVectorX { get; } + + public HsbwCommand(int leftSidebearingPointX, int characterWidthVectorX) + { + LeftSidebearingPointX = leftSidebearingPointX; + CharacterWidthVectorX = characterWidthVectorX; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs new file mode 100644 index 00000000..0031d880 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs @@ -0,0 +1,57 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline +{ + /// + /// Sets left sidebearing and the character width vector. + /// This command also sets the current point to(sbx, sby), but does not place the point in the character path. + /// + internal class SbwCommand + { + public const string Name = "sbw"; + + public static readonly byte First = 12; + public static readonly byte? Second = 7; + + public bool TakeFromStackBottom { get; } = true; + public bool ClearsOperandStack { get; } = true; + + /// + /// The X value of the left sidebearing point. + /// + public int LeftSidebearingX { get; set; } + + /// + /// The Y value of the left sidebearing point. + /// + public int LeftSidebearingY { get; set; } + + /// + /// The X value of the character width vector. + /// + public int CharacterWidthX { get; } + + /// + /// The Y value of the character width vector. + /// + public int CharacterWidthY { get; } + + /// + /// Create a new . + /// + /// The X value of the left sidebearing point. + /// The Y value of the left sidebearing point. + /// The X value of the character width vector. + /// The Y value of the character width vector. + public SbwCommand(int leftSidebearingX, int leftSidebearingY, int characterWidthX, int characterWidthY) + { + LeftSidebearingX = leftSidebearingX; + LeftSidebearingY = leftSidebearingY; + CharacterWidthX = characterWidthX; + CharacterWidthY = characterWidthY; + } + + public override string ToString() + { + return $"{LeftSidebearingX} {LeftSidebearingY} {CharacterWidthX} {CharacterWidthY} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SeacCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SeacCommand.cs new file mode 100644 index 00000000..a951879c --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/StartFinishOutline/SeacCommand.cs @@ -0,0 +1,64 @@ +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline +{ + /// + /// Standard encoding accented character. + /// Makes an accented character from two other characters in the font program. + /// + internal 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; + + /// + /// The x component of the left sidebearing of the accent. + /// + public int AccentLeftSidebearingX { get; set; } + + /// + /// The x position of the origin of the accent character relative to the base character. + /// + public int AccentOriginX { get; set; } + + /// + /// The y position of the origin of the accent character relative to the base character. + /// + public int AccentOriginY { get; set; } + + /// + /// The character code of the base character. + /// + public int BaseCharacterCode { get; set; } + + /// + /// The character code of the accent character. + /// + public int AccentCharacterCode { get; set; } + + /// + /// Create a new . + /// + /// The x component of the left sidebearing of the accent. + /// The x position of the origin of the accent character relative to the base character. + /// The y position of the origin of the accent character relative to the base character. + /// The character code of the base character. + /// The character code of the accent character. + public SeacCommand(int accentLeftSidebearingX, int accentOriginX, int accentOriginY, int baseCharacterCode, int accentCharacterCode) + { + AccentLeftSidebearingX = accentLeftSidebearingX; + AccentOriginX = accentOriginX; + AccentOriginY = accentOriginY; + BaseCharacterCode = baseCharacterCode; + AccentCharacterCode = accentCharacterCode; + } + + public override string ToString() + { + return $"{AccentLeftSidebearingX} {AccentOriginX} {AccentOriginY} {BaseCharacterCode} {AccentCharacterCode} {Name}"; + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs new file mode 100644 index 00000000..9b24f873 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings +{ + class Type1CharStringCommandFactory + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs new file mode 100644 index 00000000..d59e791f --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace UglyToad.PdfPig.Fonts.Type1.CharStrings +{ + class Type1CharStringParser + { + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1CharstringDecryptedBytes.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1CharstringDecryptedBytes.cs new file mode 100644 index 00000000..99d88fb4 --- /dev/null +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1CharstringDecryptedBytes.cs @@ -0,0 +1,38 @@ +namespace UglyToad.PdfPig.Fonts.Type1.Parser +{ + using System; + using System.Collections.Generic; + + internal class Type1CharstringDecryptedBytes + { + public IReadOnlyList Bytes { get; } + + public int Index { get; } + + public string Name { get; } + + public SourceType Source { get; } + + public Type1CharstringDecryptedBytes(IReadOnlyList bytes, int index) + { + Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); + Index = index; + Name = ".notdef"; + Source = SourceType.Subroutine; + } + + public Type1CharstringDecryptedBytes(string name, IReadOnlyList bytes, int index) + { + Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); + Index = index; + Name = name; + Source = SourceType.Charstring; + } + + public enum SourceType + { + Subroutine, + Charstring + } + } +} diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs index b1777b08..1edab9eb 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs @@ -14,6 +14,7 @@ private const int EexecRandomBytes = 4; private const int Len4Bytes = 4; private const int Password = 5839; + private const int CharstringEncryptionKey = 4330; public IReadOnlyList Parse(IReadOnlyList bytes, bool isLenientParsing) { @@ -137,11 +138,13 @@ case Type1Symbols.BlueScale: { builder.BlueScale = ReadNumeric(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.ForceBold: { builder.ForceBold = ReadBoolean(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.MinFeature: @@ -160,6 +163,10 @@ } } + builder.MinFeature = new Type1PrivateDictionary.MinFeature(procedureTokens[0].AsInt(), procedureTokens[1].AsInt()); + + ReadTillDef(tokenizer); + break; } case Type1Symbols.Password: @@ -171,12 +178,16 @@ } builder.Password = password; + + ReadTillDef(tokenizer); + break; } case Type1Symbols.UniqueId: { var id = (int)ReadNumeric(tokenizer); builder.UniqueId = id; + ReadTillDef(tokenizer); break; } case Type1Symbols.Len4: @@ -187,11 +198,13 @@ case Type1Symbols.BlueShift: { builder.BlueShift = (int)ReadNumeric(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.BlueFuzz: { builder.BlueFuzz = (int)ReadNumeric(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.FamilyBlues: @@ -207,32 +220,38 @@ case Type1Symbols.LanguageGroup: { builder.LanguageGroup = (int)ReadNumeric(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.RndStemUp: { builder.RoundStemUp = ReadBoolean(tokenizer); + ReadTillDef(tokenizer); break; } case Type1Symbols.Subroutines: { - //readSubrs(lenIV); + builder.Subroutines = ReadSubroutines(tokenizer, lenIv, isLenientParsing); break; - } case Type1Symbols.OtherSubroutines: { ReadOtherSubroutines(tokenizer, isLenientParsing); + ReadTillDef(tokenizer); break; } } } - while (tokenizer.CurrentToken != null) + var currentToken = tokenizer.CurrentToken; + while (currentToken.Type != Type1Token.TokenType.Literal + && !string.Equals(currentToken.Text, "CharStrings", StringComparison.OrdinalIgnoreCase)) { - tokenizer.GetNext(); + currentToken = tokenizer.GetNext(); } + var charStrings = ReadCharStrings(tokenizer, lenIv, isLenientParsing); + return decrypted; } @@ -242,8 +261,6 @@ /// The first byte must not be whitespace. /// One of the first four ciphertext bytes must not be an ASCII hex character. /// - /// - /// private static bool IsBinary(IReadOnlyList bytes) { if (bytes.Count < 4) @@ -428,6 +445,11 @@ { if (token.Type == Type1Token.TokenType.Name) { + if (string.Equals(token.Text, "ND", StringComparison.OrdinalIgnoreCase) || string.Equals(token.Text, "|-", StringComparison.OrdinalIgnoreCase)) + { + return; + } + if (string.Equals(token.Text, "def", StringComparison.OrdinalIgnoreCase)) { break; @@ -459,7 +481,8 @@ } } - private static IReadOnlyList ReadArrayValues(Type1Tokenizer tokenizer, Func converter, bool hasReadStart = false) + private static IReadOnlyList ReadArrayValues(Type1Tokenizer tokenizer, Func converter, bool hasReadStart = false, + bool includeDef = true) { if (!hasReadStart) { @@ -488,6 +511,11 @@ } } + if (includeDef) + { + ReadTillDef(tokenizer); + } + return results; } @@ -522,7 +550,7 @@ if (start.Type == Type1Token.TokenType.StartArray) { - ReadArrayValues(tokenizer, x => x, true); + ReadArrayValues(tokenizer, x => x, true, false); } else if (start.Type == Type1Token.TokenType.Integer || start.Type == Type1Token.TokenType.Real) { @@ -542,5 +570,99 @@ throw new InvalidOperationException($"Failed to read start of /OtherSubrs array. Got start token: {start}."); } } + + private static IReadOnlyList ReadSubroutines(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing) + { + var length = (int)ReadNumeric(tokenizer); + var subroutines = new List(length); + + ReadExpected(tokenizer, Type1Token.TokenType.Name, "array"); + + for (var i = 0; i < length; i++) + { + var current = tokenizer.GetNext(); + if (current.Type != Type1Token.TokenType.Name || !string.Equals(current.Text, "dup")) + { + break; + } + + var index = (int)ReadNumeric(tokenizer); + + var byteLength = (int)ReadNumeric(tokenizer); + + var charstring = tokenizer.GetNext(); + + if (!(charstring is Type1DataToken charstringToken)) + { + throw new InvalidOperationException($"Found an unexpected token instead of subroutine charstring: {charstring}."); + } + + if (!isLenientParsing && charstringToken.Data.Count != byteLength) + { + throw new InvalidOperationException($"The subroutine charstring {charstringToken} did not have the expected length of {byteLength}."); + } + + var subroutine = Decrypt(charstringToken.Data, CharstringEncryptionKey, lenIv); + subroutines.Add(new Type1CharstringDecryptedBytes(subroutine, index)); + ReadTillPut(tokenizer); + } + + ReadTillDef(tokenizer); + + return subroutines; + } + + private static IReadOnlyList ReadCharStrings(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing) + { + var length = (int) ReadNumeric(tokenizer); + + ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict"); + + // dup begin or CharStrings begin. + ReadExpected(tokenizer, Type1Token.TokenType.Name); + ReadExpected(tokenizer, Type1Token.TokenType.Name, "begin"); + + var results = new List(); + + for (var i = 0; i < length; i++) + { + var token = tokenizer.GetNext(); + + if (token.Type == Type1Token.TokenType.Name && + string.Equals(token.Text, "end", StringComparison.OrdinalIgnoreCase)) + { + break; + } + + if (token.Type != Type1Token.TokenType.Literal) + { + throw new InvalidOperationException($"Type 1 font error. Expected literal for charstring name, instead got: {token}."); + } + + var name = token.Text; + + var charstringLength = ReadNumeric(tokenizer); + var charstring = tokenizer.GetNext(); + if (!(charstring is Type1DataToken charstringToken)) + { + throw new InvalidOperationException($"Got wrong type of token, expected charstring, instead got: {charstring}."); + } + + if (!isLenientParsing && charstringToken.Data.Count != charstringLength) + { + throw new InvalidOperationException($"The charstring {charstringToken} did not have the expected length of {charstringLength}."); + } + + var data = Decrypt(charstringToken.Data, CharstringEncryptionKey, lenIv); + + results.Add(new Type1CharstringDecryptedBytes(name, data, i)); + + ReadTillDef(tokenizer); + } + + ReadExpected(tokenizer, Type1Token.TokenType.Name, "end"); + + return results; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs index 1ff1705c..540a74e1 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs @@ -376,6 +376,8 @@ { // Skip preceding space. bytes.MoveNext(); + // TODO: may be wrong + bytes.MoveNext(); byte[] data = new byte[length]; for (int i = 0; i < length; i++) diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs b/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs index 1779eff1..0dc0d3d1 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Fonts.Type1 { using System.Collections.Generic; + using Parser; internal class Type1PrivateDictionary { @@ -12,7 +13,7 @@ public object NoAccessDef { get; set; } - public object[] Subroutines { get; set; } + public IReadOnlyList Subroutines { get; set; } public object[] OtherSubroutines { get; set; } @@ -48,9 +49,22 @@ public int LenIv { get; set; } - public object[] MinFeature { get; set; } + public MinFeature MinFeature { get; set; } public bool RoundStemUp { get; set; } } + + public class MinFeature + { + public int First { get; } + + public int Second { get; } + + public MinFeature(int first, int second) + { + First = first; + Second = second; + } + } } } diff --git a/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj b/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj index 14382ee4..7272a579 100644 --- a/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj +++ b/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj @@ -36,5 +36,9 @@ + + + +