Issue451_Type2CharStrings parsing/intepretation error

This commit is contained in:
Fred Natzke 2022-12-26 17:08:03 +10:00
parent 2bcac59917
commit 210c0dde50
2 changed files with 67 additions and 53 deletions

View File

@ -8,6 +8,7 @@
/// </summary> /// </summary>
internal class LazyType2Command internal class LazyType2Command
{ {
private readonly int minimumStackParameters;
private readonly Action<Type2BuildCharContext> runCommand; private readonly Action<Type2BuildCharContext> runCommand;
/// <summary> /// <summary>
@ -19,10 +20,12 @@
/// Create a new <see cref="LazyType2Command"/>. /// Create a new <see cref="LazyType2Command"/>.
/// </summary> /// </summary>
/// <param name="name">The name of the command.</param> /// <param name="name">The name of the command.</param>
/// <param name="minimumStackParameters">Minimum number of argument which must be on the stack or -1 if no checking</param>
/// <param name="runCommand">The action to execute when evaluating the command. This modifies the <see cref="Type2BuildCharContext"/>.</param> /// <param name="runCommand">The action to execute when evaluating the command. This modifies the <see cref="Type2BuildCharContext"/>.</param>
public LazyType2Command(string name, Action<Type2BuildCharContext> runCommand) public LazyType2Command(string name, int minimumStackParameters, Action<Type2BuildCharContext> runCommand)
{ {
Name = name ?? throw new ArgumentNullException(nameof(name)); Name = name ?? throw new ArgumentNullException(nameof(name));
this.minimumStackParameters = minimumStackParameters;
this.runCommand = runCommand ?? throw new ArgumentNullException(nameof(runCommand)); this.runCommand = runCommand ?? throw new ArgumentNullException(nameof(runCommand));
} }
@ -38,6 +41,13 @@
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (context.Stack.Length < minimumStackParameters)
{
Debug.WriteLine($"Warning: CFF CharString command '{Name}' expected {minimumStackParameters} arguments. Got: {context.Stack.Length}. Command ignored and stack cleared.");
context.Stack.Clear();
return;
}
runCommand(context); runCommand(context);
} }

View File

@ -3,7 +3,9 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
{ {
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Charsets; using Charsets;
using Core; using Core;
@ -35,7 +37,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
private static readonly IReadOnlyDictionary<byte, LazyType2Command> SingleByteCommandStore = new Dictionary<byte, LazyType2Command> private static readonly IReadOnlyDictionary<byte, LazyType2Command> SingleByteCommandStore = new Dictionary<byte, LazyType2Command>
{ {
{ HstemByte, new LazyType2Command("hstem", ctx => { HstemByte, new LazyType2Command("hstem", 2, ctx =>
{ {
var numberOfEdgeHints = ctx.Stack.Length / 2; var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (double, double)[numberOfEdgeHints]; var hints = new (double, double)[numberOfEdgeHints];
@ -62,7 +64,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ {
VstemByte, new LazyType2Command("vstem", ctx => VstemByte, new LazyType2Command("vstem", 2, ctx =>
{ {
var numberOfEdgeHints = ctx.Stack.Length / 2; var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (double, double)[numberOfEdgeHints]; var hints = new (double, double)[numberOfEdgeHints];
@ -89,7 +91,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 4, { 4,
new LazyType2Command("vmoveto", ctx => new LazyType2Command("vmoveto", 1, ctx =>
{ {
var dy = ctx.Stack.PopBottom(); var dy = ctx.Stack.PopBottom();
@ -100,7 +102,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 5, { 5,
new LazyType2Command("rlineto", ctx => new LazyType2Command("rlineto", 2, ctx =>
{ {
var numberOfLines = ctx.Stack.Length / 2; var numberOfLines = ctx.Stack.Length / 2;
@ -116,7 +118,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 6, { 6,
new LazyType2Command("hlineto", ctx => new LazyType2Command("hlineto", 1, ctx =>
{ {
/* /*
* Appends a horizontal line of length dx1 to the current point. * Appends a horizontal line of length dx1 to the current point.
@ -152,7 +154,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 7, { 7,
new LazyType2Command("vlineto", ctx => new LazyType2Command("vlineto", 1, ctx =>
{ {
var isOdd = ctx.Stack.Length % 2 != 0; var isOdd = ctx.Stack.Length % 2 != 0;
@ -182,7 +184,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 8, { 8,
new LazyType2Command("rrcurveto", ctx => new LazyType2Command("rrcurveto", 6, ctx =>
{ {
var curveCount = ctx.Stack.Length / 6; var curveCount = ctx.Stack.Length / 6;
for (var i = 0; i < curveCount; i++) for (var i = 0; i < curveCount; i++)
@ -196,14 +198,14 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 10, new LazyType2Command("callsubr", ctx => {})}, { 10, new LazyType2Command("callsubr", 1, ctx => {})},
{ 11, new LazyType2Command("return", ctx => {})}, { 11, new LazyType2Command("return", 0, ctx => {})},
{ 14, new LazyType2Command("endchar", ctx => { 14, new LazyType2Command("endchar", 0, ctx =>
{ {
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ HstemhmByte, new LazyType2Command("hstemhm", ctx => { HstemhmByte, new LazyType2Command("hstemhm", 2, ctx =>
{ {
// Same as vstem except the charstring contains hintmask // Same as vstem except the charstring contains hintmask
var numberOfEdgeHints = ctx.Stack.Length / 2; var numberOfEdgeHints = ctx.Stack.Length / 2;
@ -231,21 +233,21 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ {
HintmaskByte, new LazyType2Command("hintmask", ctx => HintmaskByte, new LazyType2Command("hintmask", 0, ctx =>
{ {
// TODO: record this mask somewhere // TODO: record this mask somewhere
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ {
CntrmaskByte, new LazyType2Command("cntrmask", ctx => CntrmaskByte, new LazyType2Command("cntrmask", 0,ctx =>
{ {
// TODO: record this mask somewhere // TODO: record this mask somewhere
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 21, { 21,
new LazyType2Command("rmoveto", ctx => new LazyType2Command("rmoveto", 2, ctx =>
{ {
var dx = ctx.Stack.PopBottom(); var dx = ctx.Stack.PopBottom();
var dy = ctx.Stack.PopBottom(); var dy = ctx.Stack.PopBottom();
@ -260,7 +262,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 22, { 22,
new LazyType2Command("hmoveto", ctx => new LazyType2Command("hmoveto", 1, ctx =>
{ {
var dx = ctx.Stack.PopBottom(); var dx = ctx.Stack.PopBottom();
@ -270,7 +272,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ VstemhmByte, new LazyType2Command("vstemhm", ctx => { VstemhmByte, new LazyType2Command("vstemhm", 2, ctx =>
{ {
// Same as vstem except the charstring contains hintmask // Same as vstem except the charstring contains hintmask
var numberOfEdgeHints = ctx.Stack.Length / 2; var numberOfEdgeHints = ctx.Stack.Length / 2;
@ -299,7 +301,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}, },
{ {
24, 24,
new LazyType2Command("rcurveline", ctx => new LazyType2Command("rcurveline", 8, ctx =>
{ {
var numberOfCurves = (ctx.Stack.Length - 2) / 6; var numberOfCurves = (ctx.Stack.Length - 2) / 6;
for (var i = 0; i < numberOfCurves; i++) for (var i = 0; i < numberOfCurves; i++)
@ -315,7 +317,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 25, { 25,
new LazyType2Command("rlinecurve", ctx => new LazyType2Command("rlinecurve", 8, ctx =>
{ {
var numberOfLines = (ctx.Stack.Length - 6) / 2; var numberOfLines = (ctx.Stack.Length - 6) / 2;
for (var i = 0; i < numberOfLines; i++) for (var i = 0; i < numberOfLines; i++)
@ -332,7 +334,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 26, { 26,
new LazyType2Command("vvcurveto", ctx => new LazyType2Command("vvcurveto", 4, ctx =>
{ {
// dx1? {dya dxb dyb dyc}+ // dx1? {dya dxb dyb dyc}+
var hasDeltaXFirstCurve = ctx.Stack.Length % 4 != 0; var hasDeltaXFirstCurve = ctx.Stack.Length % 4 != 0;
@ -357,7 +359,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 27, new LazyType2Command("hhcurveto", ctx => { 27, new LazyType2Command("hhcurveto", 4, ctx =>
{ {
// dy1? {dxa dxb dyb dxc}+ // dy1? {dxa dxb dyb dxc}+
var hasDeltaYFirstCurve = ctx.Stack.Length % 4 != 0; var hasDeltaYFirstCurve = ctx.Stack.Length % 4 != 0;
@ -387,10 +389,10 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 29, new LazyType2Command("callgsubr", ctx => {}) { 29, new LazyType2Command("callgsubr", 1, ctx => {})
}, },
{ 30, { 30,
new LazyType2Command("vhcurveto", ctx => new LazyType2Command("vhcurveto", 4, ctx =>
{ {
var remainder = ctx.Stack.Length % 8; var remainder = ctx.Stack.Length % 8;
@ -477,7 +479,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ 31, { 31,
new LazyType2Command("hvcurveto", ctx => new LazyType2Command("hvcurveto", 4, ctx =>
{ {
var remainder = ctx.Stack.Length % 8; var remainder = ctx.Stack.Length % 8;
@ -563,70 +565,72 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 255, new LazyType2Command("unknown", x => {}) } { 255, new LazyType2Command("unknown", -1, x => {}) }
}; };
private static readonly IReadOnlyDictionary<byte, LazyType2Command> TwoByteCommandStore = new Dictionary<byte, LazyType2Command> private static readonly IReadOnlyDictionary<byte, LazyType2Command> TwoByteCommandStore = new Dictionary<byte, LazyType2Command>
{ {
{ 3, new LazyType2Command("and", ctx => ctx.Stack.Push(ctx.Stack.PopTop() != 0 && ctx.Stack.PopTop() != 0 ? 1 : 0))}, { 3, new LazyType2Command("and", 2, ctx => ctx.Stack.Push(ctx.Stack.PopTop() != 0 && ctx.Stack.PopTop() != 0 ? 1 : 0))},
{ 4, new LazyType2Command("or", ctx => { 4, new LazyType2Command("or", 2,ctx =>
{ {
var arg1 = ctx.Stack.PopTop(); var arg1 = ctx.Stack.PopTop();
var arg2 = ctx.Stack.PopTop(); var arg2 = ctx.Stack.PopTop();
ctx.Stack.Push(arg1 != 0 || arg2 != 0 ? 1 : 0); ctx.Stack.Push(arg1 != 0 || arg2 != 0 ? 1 : 0);
})}, })},
{ 5, new LazyType2Command("not", ctx => ctx.Stack.Push(ctx.Stack.PopTop() == 0 ? 1 : 0))}, { 5, new LazyType2Command("not", 1,ctx => ctx.Stack.Push(ctx.Stack.PopTop() == 0 ? 1 : 0))},
{ 9, new LazyType2Command("abs", ctx => ctx.Stack.Push(Math.Abs(ctx.Stack.PopTop())))}, { 9, new LazyType2Command("abs", 1, ctx => ctx.Stack.Push(Math.Abs(ctx.Stack.PopTop())))},
{ 10, new LazyType2Command("add", ctx => ctx.Stack.Push(ctx.Stack.PopTop() + ctx.Stack.PopTop()))}, { 10, new LazyType2Command("add", 2, ctx => ctx.Stack.Push(ctx.Stack.PopTop() + ctx.Stack.PopTop()))},
{ {
11, new LazyType2Command("sub", ctx => 11, new LazyType2Command("sub", 2, ctx =>
{ {
var num1 = ctx.Stack.PopTop(); var num1 = ctx.Stack.PopTop();
var num2 = ctx.Stack.PopTop(); var num2 = ctx.Stack.PopTop();
ctx.Stack.Push(num2 - num1); ctx.Stack.Push(num2 - num1);
}) })
}, },
{ 12, new LazyType2Command("div", ctx => ctx.Stack.Push(ctx.Stack.PopTop()/ctx.Stack.PopTop()))}, { 12, new LazyType2Command("div", 2, ctx => ctx.Stack.Push(ctx.Stack.PopTop()/ctx.Stack.PopTop()))},
{ 14, new LazyType2Command("neg", ctx => ctx.Stack.Push(-1 * Math.Abs(ctx.Stack.PopTop())))}, { 14, new LazyType2Command("neg", 1, ctx => ctx.Stack.Push(-1 * Math.Abs(ctx.Stack.PopTop())))},
// ReSharper disable once EqualExpressionComparison // ReSharper disable once EqualExpressionComparison
{ 15, new LazyType2Command("eq", ctx => ctx.Stack.Push(ctx.Stack.PopTop() == ctx.Stack.PopTop() ? 1 : 0))}, { 15, new LazyType2Command("eq", 2, ctx => ctx.Stack.Push(ctx.Stack.PopTop() == ctx.Stack.PopTop() ? 1 : 0))},
{ 18, new LazyType2Command("drop", ctx => ctx.Stack.PopTop())}, { 18, new LazyType2Command("drop", 1, ctx => ctx.Stack.PopTop())},
{ 20, new LazyType2Command("put", ctx => ctx.AddToTransientArray(ctx.Stack.PopTop(), (int)ctx.Stack.PopTop()))}, { 20, new LazyType2Command("put", 2, ctx => ctx.AddToTransientArray(ctx.Stack.PopTop(), (int)ctx.Stack.PopTop()))},
{ 21, new LazyType2Command("get", ctx => ctx.Stack.Push(ctx.GetFromTransientArray((int)ctx.Stack.PopTop())))}, { 21, new LazyType2Command("get", 1, ctx => ctx.Stack.Push(ctx.GetFromTransientArray((int)ctx.Stack.PopTop())))},
{ 22, new LazyType2Command("ifelse", x => { })}, { 22, new LazyType2Command("ifelse", 4, x => { })},
// TODO: Random, do we want to support this? // TODO: Random, do we want to support this?
{ 23, new LazyType2Command("random", ctx => ctx.Stack.Push(0.5))}, { 23, new LazyType2Command("random", 0, ctx => ctx.Stack.Push(0.5))},
{ 24, new LazyType2Command("mul", ctx => ctx.Stack.Push(ctx.Stack.PopTop() * ctx.Stack.PopTop()))}, { 24, new LazyType2Command("mul", 2, ctx => ctx.Stack.Push(ctx.Stack.PopTop() * ctx.Stack.PopTop()))},
{ 26, new LazyType2Command("sqrt", ctx => ctx.Stack.Push(Math.Sqrt(ctx.Stack.PopTop())))}, { 26, new LazyType2Command("sqrt", 1, ctx => ctx.Stack.Push(Math.Sqrt(ctx.Stack.PopTop())))},
{ {
27, new LazyType2Command("dup", ctx => 27, new LazyType2Command("dup", 1, ctx =>
{ {
var val = ctx.Stack.PopTop(); var val = ctx.Stack.PopTop();
ctx.Stack.Push(val); ctx.Stack.Push(val);
ctx.Stack.Push(val); ctx.Stack.Push(val);
}) })
}, },
{ 28, new LazyType2Command("exch", ctx => { 28, new LazyType2Command("exch", 2, ctx =>
{ {
var num1 = ctx.Stack.PopTop(); var num1 = ctx.Stack.PopTop();
var num2 = ctx.Stack.PopTop(); var num2 = ctx.Stack.PopTop();
ctx.Stack.Push(num1); ctx.Stack.Push(num1);
ctx.Stack.Push(num2); ctx.Stack.Push(num2);
})}, })},
{ 29, new LazyType2Command("index", ctx => { 29, new LazyType2Command("index", 2, ctx =>
{ {
var index = ctx.Stack.PopTop(); var index = ctx.Stack.PopTop();
var val = ctx.Stack.CopyElementAt((int) index); var val = ctx.Stack.CopyElementAt((int) index);
ctx.Stack.Push(val); ctx.Stack.Push(val);
})}, })},
{ {
30, new LazyType2Command("roll", ctx => 30, new LazyType2Command("roll", 3, ctx =>
{ {
// TODO: roll // TODO: roll
}) })
}, },
{ {
34, new LazyType2Command("hflex", ctx => 34, new LazyType2Command("hflex", 7, ctx =>
{ {
// dx1 dx2 dy2 dx3 dx4 dx5 dx6 // dx1 dx2 dy2 dx3 dx4 dx5 dx6
// Two Bezier curves with an fd of 50 // Two Bezier curves with an fd of 50
@ -636,7 +640,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}) })
}, },
{ {
35, new LazyType2Command("flex", ctx => 35, new LazyType2Command("flex", 13, ctx =>
{ {
// dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd // dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd
// Two Bezier curves will be represented as a straight line when depth less than fd character space units // Two Bezier curves will be represented as a straight line when depth less than fd character space units
@ -656,12 +660,12 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
ctx.Stack.Clear(); ctx.Stack.Clear();
}) })
}, },
{ 36, new LazyType2Command("hflex1", ctx => { 36, new LazyType2Command("hflex1", 9, ctx =>
{ {
// TODO: implement // TODO: implement
ctx.Stack.Clear(); ctx.Stack.Clear();
})}, })},
{ 37, new LazyType2Command("flex1", ctx => { 37, new LazyType2Command("flex1", 11, ctx =>
{ {
// dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 // dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6
// d6 is either dx or dy // d6 is either dx or dy
@ -769,7 +773,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
{ {
var num = bytes[++i] << 8 | bytes[++i]; var num = bytes[++i] << 8 | bytes[++i];
// Next 2 bytes are a 16-bit two's complement number. // Next 2 bytes are a 16-bit two's complement number.
return (short) (num); return (short)(num);
} }
if (b >= 32 && b <= 246) if (b >= 32 && b <= 246)