diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs index d99ec410..e73ac8e6 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/DotSectionCommand.cs @@ -1,6 +1,27 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint { + /// + /// Brackets an outline section for the dots in letters such as "i", "j" and "!". + /// 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; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs index c1d989aa..49151f8d 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStem3Command.cs @@ -1,6 +1,50 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint { + /// + /// 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. + /// + /// + /// Suited to letters with 3 horizontal stems like 'E'. + /// 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}"; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs index b067f838..8d2534e2 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/HStemCommand.cs @@ -1,6 +1,43 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint { + /// + /// 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. + /// 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; + + /// + /// The first Y coordinate for the stem zone, relative to the current left sidebearing Y point. + /// + public int Y { get; } + + /// + /// The distance to move from Y vertically for the horizontal stem zone. + /// + public int DeltaY { get; } + + /// + /// Create a new . + /// + /// The lower Y coordinate of the stem zone. + /// The distance to move from Y vertically for the stem zone. + public HStemCommand(int y, int deltaY) + { + Y = y; + DeltaY = deltaY; + } + + public override string ToString() + { + return $"{Y} {DeltaY} {Name}"; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs index 5f7314c7..6f72200f 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStem3Command.cs @@ -1,6 +1,50 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint { + /// + /// 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. + /// + /// + /// Suited to letters with 3 vertical stems, for instance 'm'. + /// 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}"; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs index ce119b5e..18503d27 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Commands/Hint/VStemCommand.cs @@ -1,6 +1,38 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.Hint { + /// + /// 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. + /// 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; + + /// + /// The first X coordinate for the stem zone, relative to the current left sidebearing X point. + /// + public int X { get; } + + /// + /// The distance to move from X horizontally for the vertical stem zone. + /// + public int DeltaX { get; } + + public VStemCommand(int x, int deltaX) + { + X = x; + DeltaX = deltaX; + } + + public override string ToString() + { + return $"{X} {DeltaX} {Name}"; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs index 9b24f873..5bf6d40f 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringCommandFactory.cs @@ -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 arguments, byte v, IReadOnlyList 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; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs index d59e791f..5582c09f 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/CharStrings/Type1CharStringParser.cs @@ -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 charStrings, IReadOnlyList subroutines) + { + foreach (var charString in charStrings) + { + ParseSingle(charString.Bytes); + } + } + + private static void ParseSingle(IReadOnlyList charStringBytes) + { + var numberStack = new List(); + 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 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; + } } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs index 1edab9eb..73f2e87e 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1EncryptedPortionParser.cs @@ -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 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 ReadCharStrings(Type1Tokenizer tokenizer, int lenIv, bool isLenientParsing) { - var length = (int) ReadNumeric(tokenizer); + var length = (int)ReadNumeric(tokenizer); ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict"); diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Symbols.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Symbols.cs index e24b8f30..69b7e1df 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Symbols.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Symbols.cs @@ -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"; + /// + /// This is shown briefly in the Type 1 spec but nowhere is it specified what it means or does. + /// + public const string Erode = "Erode"; } } diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs index 540a74e1..9c6db52b 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Parser/Type1Tokenizer.cs @@ -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++) diff --git a/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs b/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs index 0dc0d3d1..459afbe0 100644 --- a/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs +++ b/src/UglyToad.PdfPig/Fonts/Type1/Type1PrivateDictionary.cs @@ -52,6 +52,8 @@ public MinFeature MinFeature { get; set; } public bool RoundStemUp { get; set; } + + public decimal? ExpansionFactor { get; set; } } public class MinFeature