mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-10-15 03:34:52 +08:00
add more type1 charstring commands and fix type1 tests
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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";
|
||||
}
|
||||
}
|
||||
|
@@ -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++)
|
||||
|
@@ -52,6 +52,8 @@
|
||||
public MinFeature MinFeature { get; set; }
|
||||
|
||||
public bool RoundStemUp { get; set; }
|
||||
|
||||
public decimal? ExpansionFactor { get; set; }
|
||||
}
|
||||
|
||||
public class MinFeature
|
||||
|
Reference in New Issue
Block a user