create command classes for each of the commands in type 1 charstrings

This commit is contained in:
Eliot Jones
2018-10-29 22:05:18 +00:00
parent 7fab13e877
commit d8a4f0f521
33 changed files with 1004 additions and 11 deletions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>

View File

@@ -0,0 +1,54 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
using System.Collections.Generic;
/// <summary>
/// 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.
/// </summary>
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;
/// <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)
{
Arguments = arguments;
ArgumentCount = argumentCount;
OtherSubroutineIndex = otherSubroutineIndex;
}
public override string ToString()
{
var joined = string.Join(" ", Arguments);
return $"{joined} {ArgumentCount} {OtherSubroutineIndex} {Name}";
}
}
}

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
/// <summary>
/// Calls a subroutine with index from the subroutines array in the Private dictionary.
/// </summary>
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;
/// <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)
{
SubroutineIndex = subroutineIndex;
}
public override string ToString()
{
return $"{SubroutineIndex} {Name}";
}
}
}

View File

@@ -0,0 +1,27 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
/// <summary>
/// This operator returns the result of dividing num1 by num2. The result is always a real.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,28 @@
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.
/// This command is used only to retrieve a result from an OtherSubrs procedure.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,27 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
/// <summary>
/// Returns from a charstring subroutine and continues execution in the calling charstring.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,42 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Arithmetic
{
/// <summary>
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
/// </summary>
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;
/// <summary>
/// The X coordinate in character space.
/// </summary>
public int X { get; }
/// <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)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"{X} {Y} {Name}";
}
}
}

View File

@@ -0,0 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
internal class DotSectionCommand
{
}
}

View File

@@ -0,0 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
internal class HStem3Command
{
}
}

View File

@@ -0,0 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
internal class HStemCommand
{
}
}

View File

@@ -0,0 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
internal class VStem3Command
{
}
}

View File

@@ -0,0 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
internal class VStemCommand
{
}
}

View File

@@ -0,0 +1,27 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Closes a sub-path. This command does not reposition the current point.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Horizontal line-to command.
/// </summary>
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;
/// <summary>
/// The length of the horizontal line.
/// </summary>
public int DeltaX { get; }
/// <summary>
/// Create a new <see cref="HLineToCommand"/>.
/// </summary>
/// <param name="deltaX">The length of the horizontal line.</param>
public HLineToCommand(int deltaX)
{
DeltaX = deltaX;
}
public override string ToString()
{
return $"{DeltaX} {Name}";
}
}
}

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Relative move to for horizontal dimension only.
/// </summary>
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;
/// <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 override string ToString()
{
return $"{X} {Name}";
}
}
}

View File

@@ -0,0 +1,41 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// 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
{
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; }
/// <summary>
/// Create a new <see cref="HvCurveToCommand"/>.
/// </summary>
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}";
}
}
}

View File

@@ -0,0 +1,42 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Relative line-to command. Creates a line moving a distance relative to the current point.
/// </summary>
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;
/// <summary>
/// The distance to move horizontally.
/// </summary>
public int DeltaX { get; }
/// <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)
{
DeltaX = deltaX;
DeltaY = deltaY;
}
public override string ToString()
{
return $"{DeltaX} {DeltaY} {Name}";
}
}
}

View File

@@ -0,0 +1,48 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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;
/// <summary>
/// The distance to move horizontally.
/// </summary>
public int DeltaX { get; }
/// <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)
{
DeltaX = deltaX;
DeltaY = deltaY;
}
public override string ToString()
{
return $"{DeltaX} {DeltaY} {Name}";
}
}
}

View File

@@ -0,0 +1,40 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Vertical-line to command.
/// </summary>
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;
/// <summary>
/// The length of the vertical line.
/// </summary>
public int DeltaY { get; }
/// <summary>
/// Create a new <see cref="VLineToCommand"/>.
/// </summary>
/// <param name="deltaY">The length of the vertical line.</param>
public VLineToCommand(int deltaY)
{
DeltaY = deltaY;
}
public override string ToString()
{
return $"{DeltaY} {Name}";
}
}
}

View File

@@ -0,0 +1,35 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// Vertical move to. Moves relative to the current point.
/// </summary>
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;
/// <summary>
/// The distance to move vertically.
/// </summary>
public int DeltaY { get; }
/// <summary>
/// Create a new <see cref="VMoveToCommand"/>.
/// </summary>
/// <param name="deltaY">The distance to move vertically.</param>
public VMoveToCommand(int deltaY)
{
DeltaY = deltaY;
}
public override string ToString()
{
return $"{DeltaY} {Name}";
}
}
}

View File

@@ -0,0 +1,34 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,28 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline
{
/// <summary>
/// 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
{
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;
}
}
}

View File

@@ -0,0 +1,29 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline
{
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -0,0 +1,57 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline
{
/// <summary>
/// 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.
/// </summary>
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;
/// <summary>
/// The X value of the left sidebearing point.
/// </summary>
public int LeftSidebearingX { get; set; }
/// <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)
{
LeftSidebearingX = leftSidebearingX;
LeftSidebearingY = leftSidebearingY;
CharacterWidthX = characterWidthX;
CharacterWidthY = characterWidthY;
}
public override string ToString()
{
return $"{LeftSidebearingX} {LeftSidebearingY} {CharacterWidthX} {CharacterWidthY} {Name}";
}
}
}

View File

@@ -0,0 +1,64 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.StartFinishOutline
{
/// <summary>
/// Standard encoding accented character.
/// Makes an accented character from two other characters in the font program.
/// </summary>
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;
/// <summary>
/// The x component of the left sidebearing of the accent.
/// </summary>
public int AccentLeftSidebearingX { get; set; }
/// <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)
{
AccentLeftSidebearingX = accentLeftSidebearingX;
AccentOriginX = accentOriginX;
AccentOriginY = accentOriginY;
BaseCharacterCode = baseCharacterCode;
AccentCharacterCode = accentCharacterCode;
}
public override string ToString()
{
return $"{AccentLeftSidebearingX} {AccentOriginX} {AccentOriginY} {BaseCharacterCode} {AccentCharacterCode} {Name}";
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
class Type1CharStringCommandFactory
{
}
}

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
class Type1CharStringParser
{
}
}

View File

@@ -0,0 +1,38 @@
namespace UglyToad.PdfPig.Fonts.Type1.Parser
{
using System;
using System.Collections.Generic;
internal class Type1CharstringDecryptedBytes
{
public IReadOnlyList<byte> Bytes { get; }
public int Index { get; }
public string Name { get; }
public SourceType Source { get; }
public Type1CharstringDecryptedBytes(IReadOnlyList<byte> bytes, int index)
{
Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
Index = index;
Name = ".notdef";
Source = SourceType.Subroutine;
}
public Type1CharstringDecryptedBytes(string name, IReadOnlyList<byte> bytes, int index)
{
Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
Index = index;
Name = name;
Source = SourceType.Charstring;
}
public enum SourceType
{
Subroutine,
Charstring
}
}
}

View File

@@ -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<byte> Parse(IReadOnlyList<byte> 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.
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
private static bool IsBinary(IReadOnlyList<byte> 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<T> ReadArrayValues<T>(Type1Tokenizer tokenizer, Func<Type1Token, T> converter, bool hasReadStart = false)
private static IReadOnlyList<T> ReadArrayValues<T>(Type1Tokenizer tokenizer, Func<Type1Token, T> 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<Type1CharstringDecryptedBytes> ReadSubroutines(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing)
{
var length = (int)ReadNumeric(tokenizer);
var subroutines = new List<Type1CharstringDecryptedBytes>(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<Type1CharstringDecryptedBytes> 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<Type1CharstringDecryptedBytes>();
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;
}
}
}

View File

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

View File

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

View File

@@ -36,5 +36,9 @@
<EmbeddedResource Include="Resources\CMap\*" />
<EmbeddedResource Include="Resources\GlyphList\*" />
</ItemGroup>
<ItemGroup>
<Folder Include="Fonts\Type1\CharStrings\Commands\Subroutine\" />
</ItemGroup>
</Project>