add more type1 charstring commands and fix type1 tests

This commit is contained in:
Eliot Jones
2018-10-30 22:59:21 +00:00
parent d8a4f0f521
commit 61e2a0814d
11 changed files with 318 additions and 22 deletions

View File

@@ -1,6 +1,27 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
/// <summary>
/// Brackets an outline section for the dots in letters such as "i", "j" and "!".
/// </summary>
internal 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 DotSectionCommand Instance { get; } = new DotSectionCommand();
private DotSectionCommand()
{
}
public override string ToString()
{
return Name;
}
}
}

View File

@@ -1,6 +1,50 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
/// <summary>
/// Declares the vertical ranges of three horizontal stem zones:
/// 1st: between y0 and y0 + dy0
/// 2nd: between y1 and y1 + dy1
/// 3rd: between y2 and y2 + dy2
/// Where y0, y1 and y2 are all relative to the y coordinate of the left sidebearing point.
/// </summary>
/// <remarks>
/// Suited to letters with 3 horizontal stems like 'E'.
/// </remarks>
internal 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 int Y0 { get; }
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)
{
Y0 = y0;
DeltaY0 = deltaY0;
Y1 = y1;
DeltaY1 = deltaY1;
Y2 = y2;
DeltaY2 = deltaY2;
}
public override string ToString()
{
return $"{Y0} {DeltaY0} {Y1} {DeltaY1} {Y2} {DeltaY2} {Name}";
}
}
}

View File

@@ -1,6 +1,43 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
/// <summary>
/// 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
{
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;
/// <summary>
/// The first Y coordinate for the stem zone, relative to the current left sidebearing Y point.
/// </summary>
public int Y { get; }
/// <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)
{
Y = y;
DeltaY = deltaY;
}
public override string ToString()
{
return $"{Y} {DeltaY} {Name}";
}
}
}

View File

@@ -1,6 +1,50 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
/// <summary>
/// Declares the horizontal ranges of three vertical stem zones.
/// 1st: between x0 and x0 + dx0
/// 2nd: between x1 and x1 + dx1
/// 3rd: between x2 and x2 + dx2
/// Where x0, x1 and x2 are all relative to the x coordinate of the left sidebearing point.
/// </summary>
/// <remarks>
/// Suited to letters with 3 vertical stems, for instance 'm'.
/// </remarks>
internal class VStem3Command
{
public const string Name = "vstem3";
public static readonly byte First = 12;
public static readonly byte? Second = 1;
public bool TakeFromStackBottom { get; } = true;
public bool ClearsOperandStack { get; } = true;
public int X0 { get; }
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)
{
X0 = x0;
DeltaX0 = deltaX0;
X1 = x1;
DeltaX1 = deltaX1;
X2 = x2;
DeltaX2 = deltaX2;
}
public override string ToString()
{
return $"{X0} {DeltaX0} {X1} {DeltaX1} {X2} {DeltaX2} {Name}";
}
}
}

View File

@@ -1,6 +1,38 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint
{
/// <summary>
/// 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
{
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;
/// <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)
{
X = x;
DeltaX = deltaX;
}
public override string ToString()
{
return $"{X} {DeltaX} {Name}";
}
}
}

View File

@@ -1,10 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
class Type1CharStringCommandFactory
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,10 +1,60 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
{
class Type1CharStringParser
using System.Collections.Generic;
using Parser;
internal class Type1CharStringParser
{
public static void Parse(IReadOnlyList<Type1CharstringDecryptedBytes> charStrings, IReadOnlyList<Type1CharstringDecryptedBytes> subroutines)
{
foreach (var charString in charStrings)
{
ParseSingle(charString.Bytes);
}
}
private static void ParseSingle(IReadOnlyList<byte> charStringBytes)
{
var numberStack = new List<int>();
for (var i = 0; i < charStringBytes.Count; i++)
{
var b = charStringBytes[i];
if (b <= 31)
{
var command = Type1CharStringCommandFactory.GetCommand(numberStack, b, charStringBytes, ref i);
}
else
{
var val = InterpretNumber(b, charStringBytes, ref i);
numberStack.Add(val);
}
}
}
private static int InterpretNumber(byte b, IReadOnlyList<byte> bytes, ref int i)
{
if (b >= 32 && b <= 246)
{
return b - 139;
}
if (b >= 247 && b <= 250)
{
var w = bytes[++i];
return ((b - 247) * 256) + w + 108;
}
if (b >= 251 && b <= 254)
{
var w = bytes[++i];
return -((b - 251) * 256) - w - 108;
}
var result = bytes[++i] << 24 + bytes[++i] << 16 + bytes[++i] << 8 + bytes[++i];
return result;
}
}
}

View File

@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CharStrings;
using IO;
using PdfPig.Parser.Parts;
using Tokenization.Tokens;
@@ -69,8 +70,9 @@
for (var i = 0; i < length; i++)
{
var token = tokenizer.GetNext();
// premature end
if (token.Type != Type1Token.TokenType.Literal)
if (token == null || token.Type != Type1Token.TokenType.Literal)
{
break;
}
@@ -80,18 +82,21 @@
switch (key)
{
case Type1Symbols.RdProcedure:
case Type1Symbols.RdProcedureAlt:
{
var procedureTokens = ReadProcedure(tokenizer);
ReadTillDef(tokenizer);
break;
}
case Type1Symbols.NoAccessDef:
case Type1Symbols.NoAccessDefAlt:
{
var procedureTokens = ReadProcedure(tokenizer);
ReadTillDef(tokenizer);
break;
}
case Type1Symbols.NoAccessPut:
case Type1Symbols.NoAccessPutAlt:
{
var procedureTokens = ReadProcedure(tokenizer);
ReadTillDef(tokenizer);
@@ -193,6 +198,7 @@
case Type1Symbols.Len4:
{
lenIv = (int)ReadNumeric(tokenizer);
ReadTillDef(tokenizer);
break;
}
case Type1Symbols.BlueShift:
@@ -240,17 +246,51 @@
ReadTillDef(tokenizer);
break;
}
case Type1Symbols.ExpansionFactor:
{
builder.ExpansionFactor = ReadNumeric(tokenizer);
ReadTillDef(tokenizer);
break;
}
case Type1Symbols.Erode:
{
ReadTillDef(tokenizer, true);
break;
}
default:
{
ReadTillDef(tokenizer, true);
break;
}
}
}
var currentToken = tokenizer.CurrentToken;
while (currentToken.Type != Type1Token.TokenType.Literal
&& !string.Equals(currentToken.Text, "CharStrings", StringComparison.OrdinalIgnoreCase))
IReadOnlyList<Type1CharstringDecryptedBytes> charStrings;
if (currentToken != null)
{
currentToken = tokenizer.GetNext();
while (currentToken != null && currentToken.Type != Type1Token.TokenType.Literal
&& !string.Equals(currentToken.Text, "CharStrings", StringComparison.OrdinalIgnoreCase))
{
currentToken = tokenizer.GetNext();
}
if (currentToken != null)
{
charStrings = ReadCharStrings(tokenizer, lenIv, isLenientParsing);
}
else
{
charStrings = new Type1CharstringDecryptedBytes[0];
}
}
else
{
charStrings = new Type1CharstringDecryptedBytes[0];
}
var charStrings = ReadCharStrings(tokenizer, lenIv, isLenientParsing);
Type1CharStringParser.Parse(charStrings, builder.Subroutines ?? new Type1CharstringDecryptedBytes[0]);
return decrypted;
}
@@ -438,7 +478,7 @@
}
}
private static void ReadTillDef(Type1Tokenizer tokenizer)
private static void ReadTillDef(Type1Tokenizer tokenizer, bool skip = false)
{
Type1Token token;
while ((token = tokenizer.GetNext()) != null)
@@ -455,7 +495,7 @@
break;
}
}
else
else if (!skip)
{
throw new InvalidOperationException($"Encountered unexpected non-name token while reading till 'def' token: {token}");
}
@@ -614,7 +654,7 @@
private static IReadOnlyList<Type1CharstringDecryptedBytes> ReadCharStrings(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing)
{
var length = (int) ReadNumeric(tokenizer);
var length = (int)ReadNumeric(tokenizer);
ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict");

View File

@@ -14,7 +14,9 @@
public const string Len4 = "lenIV";
public const string MinFeature = "MinFeature";
public const string NoAccessDef = "ND";
public const string NoAccessDefAlt = "|-";
public const string NoAccessPut = "NP";
public const string NoAccessPutAlt = "|";
public const string OtherBlues = "OtherBlues";
public const string OtherSubroutines = "OtherSubrs";
public const string Password = "password";
@@ -27,5 +29,9 @@
public const string StemSnapVerticalWidths = "StemSnapV";
public const string Subroutines = "Subrs";
public const string UniqueId = "UniqueID";
/// <summary>
/// This is shown briefly in the Type 1 spec but nowhere is it specified what it means or does.
/// </summary>
public const string Erode = "Erode";
}
}

View File

@@ -115,7 +115,7 @@
throw new InvalidOperationException($"The binary portion of the type 1 font was invalid at position {bytes.CurrentOffset}.");
}
if (name.Equals("RD") || name.Equals("-|"))
if (name.Equals(Type1Symbols.RdProcedure, StringComparison.OrdinalIgnoreCase) || name.Equals(Type1Symbols.RdProcedureAlt))
{
if (previousToken.Type == Type1Token.TokenType.Integer)
{
@@ -377,7 +377,7 @@
// Skip preceding space.
bytes.MoveNext();
// TODO: may be wrong
bytes.MoveNext();
// bytes.MoveNext();
byte[] data = new byte[length];
for (int i = 0; i < length; i++)

View File

@@ -52,6 +52,8 @@
public MinFeature MinFeature { get; set; }
public bool RoundStemUp { get; set; }
public decimal? ExpansionFactor { get; set; }
}
public class MinFeature