add charset interface, create class to store cff font data. add the command logic for type 2 charstrings #6

This commit is contained in:
Eliot Jones
2018-11-17 14:59:58 +00:00
parent 7358650b0a
commit 4d18a2478d
21 changed files with 988 additions and 161 deletions

View File

@@ -62,6 +62,16 @@
stack.Add(value);
}
public decimal CopyElementAt(int index)
{
if (index < 0)
{
return stack[stack.Count - 1];
}
return stack[index];
}
/// <summary>
/// Removes all values from the stack.
/// </summary>

View File

@@ -1,7 +1,9 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Geometry;
/// <summary>
/// Represents the deferred execution of a Type 2 CharString command.
@@ -32,6 +34,67 @@
internal class Type2BuildCharContext
{
private readonly Dictionary<int, decimal> transientArray = new Dictionary<int, decimal>();
public CharStringStack Stack { get; } = new CharStringStack();
public CharacterPath Path { get; } = new CharacterPath();
public PdfPoint CurrentLocation { get; set; } = new PdfPoint(0, 0);
public void AddRelativeHorizontalLine(decimal dx)
{
AddRelativeLine(dx, 0);
}
public void AddRelativeVerticalLine(decimal dy)
{
AddRelativeLine(0, dy);
}
public void AddRelativeBezierCurve(decimal dx1, decimal dy1, decimal dx2, decimal dy2, decimal dx3, decimal dy3)
{
var x1 = CurrentLocation.X + dx1;
var y1 = CurrentLocation.Y + dy1;
var x2 = x1 + dx2;
var y2 = y1 + dy2;
var x3 = x2 + dx3;
var y3 = y2 + dy3;
Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
CurrentLocation = new PdfPoint(x3, y3);
}
public void AddRelativeLine(decimal dx, decimal dy)
{
var dest = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
Path.LineTo(dest.X, dest.Y);
CurrentLocation = dest;
}
public void AddVerticalStemHints(IReadOnlyList<(decimal start, decimal end)> hints)
{
}
public void AddHorizontalStemHints(IReadOnlyList<(decimal start, decimal end)> hints)
{
}
public void AddToTransientArray(decimal value, int location)
{
transientArray[location] = value;
}
public decimal GetFromTransientArray(int location)
{
var result = transientArray[location];
transientArray.Remove(location);
return result;
}
}
}

View File

@@ -1,9 +1,15 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
{
using System;
using System.Collections.Generic;
using Geometry;
using Util;
using Util.JetBrains.Annotations;
/// <summary>
/// Decodes the commands and numbers making up a Type 2 CharString. A Type 2 CharString extends on the Type 1 CharString format.
/// Compared to the Type 1 format, the Type 2 encoding offers smaller size and an opportunity for better rendering quality and
/// performance. The Type 2 charstring operators are (with one exception) a superset of the Type 1 operators.
/// </summary>
/// <remarks>
/// A Type 2 charstring program is a sequence of unsigned 8-bit bytes that encode numbers and operators.
@@ -11,8 +17,669 @@
/// </remarks>
internal class Type2CharStringParser
{
public static Type2CharStrings Parse(IReadOnlyList<IReadOnlyList<byte>> charStringBytes)
private static readonly IReadOnlyDictionary<int, LazyType2Command> SingleByteCommandStore = new Dictionary<int, LazyType2Command>
{
{ 1, new LazyType2Command("hstem", ctx =>
{
var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (decimal, decimal)[numberOfEdgeHints];
var firstStartY = ctx.Stack.PopBottom();
var endY = firstStartY + ctx.Stack.PopBottom();
hints[0] = (firstStartY, endY);
var currentY = endY;
for (var i = 1; i < numberOfEdgeHints; i++)
{
var dyStart = ctx.Stack.PopBottom();
var dyEnd = ctx.Stack.PopBottom();
hints[i] = (currentY + dyStart, currentY + dyStart + dyEnd);
currentY = currentY + dyStart + dyEnd;
}
ctx.AddHorizontalStemHints(hints);
ctx.Stack.Clear();
})
},
{
3, new LazyType2Command("vstem", ctx =>
{
var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (decimal, decimal)[numberOfEdgeHints];
var firstStartX = ctx.Stack.PopBottom();
var endX = firstStartX + ctx.Stack.PopBottom();
hints[0] = (firstStartX, endX);
var currentX = endX;
for (var i = 1; i < numberOfEdgeHints; i++)
{
var dxStart = ctx.Stack.PopBottom();
var dxEnd = ctx.Stack.PopBottom();
hints[i] = (currentX + dxStart, currentX + dxStart + dxEnd);
currentX = currentX + dxStart + dxEnd;
}
ctx.AddVerticalStemHints(hints);
ctx.Stack.Clear();
})
},
{ 4,
new LazyType2Command("vmoveto", ctx =>
{
var dy = ctx.Stack.PopBottom();
ctx.Path.MoveTo(ctx.CurrentLocation.X, ctx.CurrentLocation.Y + dy);
ctx.CurrentLocation = ctx.CurrentLocation.MoveY(dy);
ctx.Stack.Clear();
})
},
{ 5,
new LazyType2Command("rlineto", ctx =>
{
var numberOfLines = ctx.Stack.Length / 2;
for (var i = 0; i < numberOfLines; i++)
{
var dxa = ctx.Stack.PopBottom();
var dya = ctx.Stack.PopBottom();
ctx.AddRelativeLine(dxa, dya);
}
ctx.Stack.Clear();
})
},
{ 6,
new LazyType2Command("hlineto", ctx =>
{
/*
* Appends a horizontal line of length dx1 to the current point.
* With an odd number of arguments, subsequent argument pairs are interpreted as alternating values of dy and dx.
* With an even number of arguments, the arguments are interpreted as alternating horizontal and vertical lines (dx and dy).
* The number of lines is determined from the number of arguments on the stack.
*/
var isOdd = ctx.Stack.Length % 2 != 0;
var dx1 = ctx.Stack.PopBottom();
ctx.AddRelativeHorizontalLine(dx1);
var numberOfAdditionalLines = ctx.Stack.Length;
for (var i = 0; i < numberOfAdditionalLines; i++)
{
var isDeltaY = (isOdd && i % 2 == 0) || (!isOdd && i % 2 != 0);
if (isDeltaY)
{
var dya = ctx.Stack.PopBottom();
ctx.AddRelativeVerticalLine(dya);
}
else
{
var dxa = ctx.Stack.PopBottom();
ctx.AddRelativeHorizontalLine(dxa);
}
}
ctx.Stack.Clear();
})
},
{ 7,
new LazyType2Command("vlineto", ctx =>
{
var isOdd = ctx.Stack.Length % 2 != 0;
var dy1 = ctx.Stack.PopBottom();
ctx.AddRelativeVerticalLine(dy1);
var numberOfAdditionalLines = ctx.Stack.Length;
for (var i = 0; i < numberOfAdditionalLines; i++)
{
var isDeltaY = (isOdd && i % 2 != 0) || (!isOdd && i % 2 == 0);
if (isDeltaY)
{
var dya = ctx.Stack.PopBottom();
ctx.AddRelativeVerticalLine(dya);
}
else
{
var dxa = ctx.Stack.PopBottom();
ctx.AddRelativeHorizontalLine(dxa);
}
}
ctx.Stack.Clear();
})
},
{ 8,
new LazyType2Command("rrcurveto", ctx =>
{
var curveCount = ctx.Stack.Length / 6;
for (var i = 0; i < curveCount; i++)
{
ctx.AddRelativeBezierCurve(ctx.Stack.PopBottom(), ctx.Stack.PopBottom(), ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom());
}
ctx.Stack.Clear();
})
},
{ 10, new LazyType2Command("callsubr", x => { })},
{ 11, new LazyType2Command("return", x => { })},
{ 14, new LazyType2Command("endchar", ctx =>
{
ctx.Stack.Clear();
})
},
{ 18, new LazyType2Command("hstemhm", ctx =>
{
// Same as vstem except the charstring contains hintmask
var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (decimal, decimal)[numberOfEdgeHints];
var firstStartY = ctx.Stack.PopBottom();
var endY = firstStartY + ctx.Stack.PopBottom();
hints[0] = (firstStartY, endY);
var currentY = endY;
for (var i = 1; i < numberOfEdgeHints; i++)
{
var dyStart = ctx.Stack.PopBottom();
var dyEnd = ctx.Stack.PopBottom();
hints[i] = (currentY + dyStart, currentY + dyStart + dyEnd);
currentY = currentY + dyStart + dyEnd;
}
ctx.AddHorizontalStemHints(hints);
ctx.Stack.Clear();
})
},
{
19, new LazyType2Command("hintmask", ctx =>
{
// TODO: record this mask somewhere
ctx.Stack.Clear();
})
},
{
20, new LazyType2Command("cntrmask", ctx =>
{
// TODO: record this mask somewhere
ctx.Stack.Clear();
})
},
{ 21,
new LazyType2Command("rmoveto", ctx =>
{
var dx = ctx.Stack.PopBottom();
var dy = ctx.Stack.PopBottom();
var newLocation = new PdfPoint(ctx.CurrentLocation.X + dx,
ctx.CurrentLocation.Y + dy);
ctx.Path.MoveTo(newLocation.X, newLocation.Y);
ctx.CurrentLocation = newLocation;
ctx.Stack.Clear();
})
},
{ 22,
new LazyType2Command("hmoveto", ctx =>
{
var dx = ctx.Stack.PopBottom();
ctx.Path.MoveTo(ctx.CurrentLocation.X + dx, ctx.CurrentLocation.Y);
ctx.CurrentLocation = ctx.CurrentLocation.MoveX(dx);
ctx.Stack.Clear();
})
},
{ 23, new LazyType2Command("vstemh", ctx =>
{
// Same as vstem except the charstring contains hintmask
var numberOfEdgeHints = ctx.Stack.Length / 2;
var hints = new (decimal, decimal)[numberOfEdgeHints];
var firstStartX = ctx.Stack.PopBottom();
var endX = firstStartX + ctx.Stack.PopBottom();
hints[0] = (firstStartX, endX);
var currentX = endX;
for (var i = 1; i < numberOfEdgeHints; i++)
{
var dxStart = ctx.Stack.PopBottom();
var dxEnd = ctx.Stack.PopBottom();
hints[i] = (currentX + dxStart, currentX + dxStart + dxEnd);
currentX = currentX + dxStart + dxEnd;
}
ctx.AddVerticalStemHints(hints);
ctx.Stack.Clear();
})
},
{
24,
new LazyType2Command("rcurveline", ctx =>
{
var numberOfCurves = (ctx.Stack.Length - 2) / 6;
for (var i = 0; i < numberOfCurves; i++)
{
ctx.AddRelativeBezierCurve(ctx.Stack.PopBottom(), ctx.Stack.PopBottom(), ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom());
}
ctx.AddRelativeLine(ctx.Stack.PopBottom(), ctx.Stack.PopBottom());
ctx.Stack.Clear();
})
},
{ 25,
new LazyType2Command("rlinecurve", ctx =>
{
var numberOfLines = (ctx.Stack.Length - 6) / 2;
for (var i = 0; i < numberOfLines; i++)
{
ctx.AddRelativeLine(ctx.Stack.PopBottom(), ctx.Stack.PopBottom());
}
ctx.AddRelativeBezierCurve(ctx.Stack.PopBottom(), ctx.Stack.PopBottom(), ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom());
ctx.Stack.Clear();
})
},
{ 26,
new LazyType2Command("vvcurveto", ctx =>
{
// dx1? {dya dxb dyb dyc}+
var hasDeltaXFirstCurve = ctx.Stack.Length % 4 != 0;
var numberOfCurves = ctx.Stack.Length / 4;
for (var i = 0; i < numberOfCurves; i++)
{
var dx1 = 0m;
if (i == 0 && hasDeltaXFirstCurve)
{
dx1 = ctx.Stack.PopBottom();
}
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, dy1, dx2, dy2, 0, dy3);
}
})
},
{ 27, new LazyType2Command("hhcurveto", ctx =>
{
var hasDeltaYFirstCurve = ctx.Stack.Length % 4 != 0;
if (hasDeltaYFirstCurve)
{
var dy1 = ctx.Stack.PopBottom();
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, dy1, dx2, dy2, dx3, 0);
}
var numberOfCurves = ctx.Stack.Length / 4;
for (var i = 0; i < numberOfCurves; i++)
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, dx3, 0);
}
ctx.Stack.Clear();
})
},
{ 29, new LazyType2Command("callgsubr", x => { })},
{ 30,
new LazyType2Command("vhcurveto", ctx =>
{
var remainder = ctx.Stack.Length % 8;
if (remainder <= 1)
{
// {dya dxb dyb dxc dxd dxe dye dyf}+ dxf?
// 2 curves, 1st starts vertical ends horizontal, second starts horizontal ends vertical
var numberOfCurves = (ctx.Stack.Length - remainder)/8;
for (var i = 0; i < numberOfCurves; i++)
{
// First curve
{
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(0, dy1, dx2, dy2, dx3, 0);
}
// Second curve
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
var dx3 = 0m;
if (i == numberOfCurves - 1 && remainder == 1)
{
dx3 = ctx.Stack.PopBottom();
}
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, dx3, dy3);
}
}
}
else if (remainder == 4 || remainder == 5)
{
// dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
var numberOfCurves = (ctx.Stack.Length - remainder) / 8;
{
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(0, dy1, dx2, dy2, dx3, 0);
}
for (var i = 0; i < numberOfCurves; i++)
{
// First curve
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, 0, dy3);
}
// Second curve
{
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
var dy3 = 0m;
if (i == numberOfCurves - 1 && remainder == 5)
{
dy3 = ctx.Stack.PopBottom();
}
ctx.AddRelativeBezierCurve(0, dy1, dx2, dy2, dx3, dy3);
}
}
}
else
{
throw new InvalidOperationException($"Unexpected number of arguments for vhcurve to: {ctx.Stack.Length}.");
}
ctx.Stack.Clear();
})
},
{ 31,
new LazyType2Command("hvcurveto", ctx =>
{
var remainder = ctx.Stack.Length % 8;
if (remainder <= 1)
{
// {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
// 2 curves, 1st starts horizontal ends vertical, second starts vertical ends horizontal
var numberOfCurves = (ctx.Stack.Length - remainder)/8;
for (var i = 0; i < numberOfCurves; i++)
{
// First curve
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, 0, dy3);
}
// Second curve
{
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
var dy3 = 0m;
if (i == numberOfCurves - 1 && remainder == 1)
{
dy3 = ctx.Stack.PopBottom();
}
ctx.AddRelativeBezierCurve(0, dy1, dx2, dy2, dx3, dy3);
}
}
}
else if (remainder == 4 || remainder == 5)
{
// dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
var numberOfCurves = (ctx.Stack.Length - remainder) / 8;
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, 0, dy3);
}
for (var i = 0; i < numberOfCurves; i++)
{
// First curve
{
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
ctx.AddRelativeBezierCurve(0, dy1, dx2, dy2, dx3, 0);
}
// Second curve
{
var dx1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
var dx3 = 0m;
if (i == numberOfCurves - 1 && remainder == 5)
{
dx3 = ctx.Stack.PopBottom();
}
ctx.AddRelativeBezierCurve(dx1, 0, dx2, dy2, dx3, dy3);
}
}
}
else
{
throw new InvalidOperationException($"Unexpected number of arguments for hvcurve to: {ctx.Stack.Length}.");
}
ctx.Stack.Clear();
})
}
};
private static readonly IReadOnlyDictionary<int, LazyType2Command> TwoByteCommandStore = new Dictionary<int, LazyType2Command>
{
{ 3, new LazyType2Command("and", ctx => ctx.Stack.Push(ctx.Stack.PopTop() != 0 && ctx.Stack.PopTop() != 0 ? 1 : 0))},
{ 4, new LazyType2Command("or", ctx =>
{
var arg1 = ctx.Stack.PopTop();
var arg2 = ctx.Stack.PopTop();
ctx.Stack.Push(arg1 != 0 || arg2 != 0 ? 1 : 0);
})},
{ 5, new LazyType2Command("not", ctx => ctx.Stack.Push(ctx.Stack.PopTop() == 0 ? 1 : 0))},
{ 9, new LazyType2Command("abs", ctx => ctx.Stack.Push(Math.Abs(ctx.Stack.PopTop())))},
{ 10, new LazyType2Command("add", ctx => ctx.Stack.Push(ctx.Stack.PopTop() + ctx.Stack.PopTop()))},
{
11, new LazyType2Command("sub", ctx =>
{
var num1 = ctx.Stack.PopTop();
var num2 = ctx.Stack.PopTop();
ctx.Stack.Push(num2 - num1);
})
},
{ 12, new LazyType2Command("div", ctx => ctx.Stack.Push(ctx.Stack.PopTop()/ctx.Stack.PopTop()))},
{ 14, new LazyType2Command("neg", ctx => ctx.Stack.Push(-1 * Math.Abs(ctx.Stack.PopTop())))},
// ReSharper disable once EqualExpressionComparison
{ 15, new LazyType2Command("eq", ctx => ctx.Stack.Push(ctx.Stack.PopTop() == ctx.Stack.PopTop() ? 1 : 0))},
{ 18, new LazyType2Command("drop", ctx => ctx.Stack.PopTop())},
{ 20, new LazyType2Command("put", ctx => ctx.AddToTransientArray(ctx.Stack.PopTop(), (int)ctx.Stack.PopTop()))},
{ 21, new LazyType2Command("get", ctx => ctx.Stack.Push(ctx.GetFromTransientArray((int)ctx.Stack.PopTop())))},
{ 22, new LazyType2Command("ifelse", x => { })},
// TODO: Random, do we want to support this?
{ 23, new LazyType2Command("random", ctx => ctx.Stack.Push(0.5m))},
{ 24, new LazyType2Command("mul", ctx => ctx.Stack.Push(ctx.Stack.PopTop() * ctx.Stack.PopTop()))},
{ 26, new LazyType2Command("sqrt", ctx => ctx.Stack.Push((decimal)Math.Sqrt((double)ctx.Stack.PopTop())))},
{
27, new LazyType2Command("dup", ctx =>
{
var val = ctx.Stack.PopTop();
ctx.Stack.Push(val);
ctx.Stack.Push(val);
})
},
{ 28, new LazyType2Command("exch", ctx =>
{
var num1 = ctx.Stack.PopTop();
var num2 = ctx.Stack.PopTop();
ctx.Stack.Push(num1);
ctx.Stack.Push(num2);
})},
{ 29, new LazyType2Command("index", ctx =>
{
var index = ctx.Stack.PopTop();
var val = ctx.Stack.CopyElementAt((int) index);
ctx.Stack.Push(val);
})},
{
30, new LazyType2Command("roll", ctx =>
{
// TODO: roll
})
},
{
34, new LazyType2Command("hflex", ctx =>
{
// dx1 dx2 dy2 dx3 dx4 dx5 dx6
// Two Bezier curves with an fd of 50
// TODO: implement
ctx.Stack.Clear();
})
},
{
35, new LazyType2Command("flex", ctx =>
{
// 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
ctx.AddRelativeBezierCurve(ctx.Stack.PopBottom(), ctx.Stack.PopBottom(), ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom());
ctx.AddRelativeBezierCurve(ctx.Stack.PopBottom(), ctx.Stack.PopBottom(), ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom(),
ctx.Stack.PopBottom());
ctx.Stack.PopBottom();
// TODO: record flex depth for this Bezier pair
ctx.Stack.Clear();
})
},
{ 36, new LazyType2Command("hflex1", ctx =>
{
// TODO: implement
ctx.Stack.Clear();
})},
{ 37, new LazyType2Command("flex1", ctx =>
{
// dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6
// d6 is either dx or dy
var dx1 = ctx.Stack.PopBottom();
var dy1 = ctx.Stack.PopBottom();
var dx2 = ctx.Stack.PopBottom();
var dy2 = ctx.Stack.PopBottom();
var dx3 = ctx.Stack.PopBottom();
var dy3 = ctx.Stack.PopBottom();
var dx4 = ctx.Stack.PopBottom();
var dy4 = ctx.Stack.PopBottom();
var dx5 = ctx.Stack.PopBottom();
var dy5 = ctx.Stack.PopBottom();
var d6 = ctx.Stack.PopBottom();
var dx = dx1 + dx2 + dx3 + dx4 + dx5;
var dy = dy1 + dy2 + dy3 + dy4 + dy5;
var lastPointIsX = Math.Abs(dx) > Math.Abs(dy);
ctx.AddRelativeBezierCurve(dx1, dy1, dx2, dy2, dx3, dy3);
ctx.AddRelativeBezierCurve(dx4, dy4, dx5, dy5, lastPointIsX ? d6 : 0, lastPointIsX ? 0 : d6);
ctx.Stack.Clear();
})},
};
public static Type2CharStrings Parse([NotNull] IReadOnlyList<IReadOnlyList<byte>> charStringBytes,
[NotNull] CompactFontFormatIndex localSubroutines,
[NotNull] CompactFontFormatIndex globalSubroutines)
{
if (charStringBytes == null)
{
throw new ArgumentNullException(nameof(charStringBytes));
}
if (localSubroutines == null)
{
throw new ArgumentNullException(nameof(localSubroutines));
}
if (globalSubroutines == null)
{
throw new ArgumentNullException(nameof(globalSubroutines));
}
var charStrings = new Dictionary<int, Type2CharStrings.CommandSequence>();
for (var i = 0; i < charStringBytes.Count; i++)
{
@@ -85,61 +752,6 @@
return lead + (fractionalPart / 65535m);
}
private static readonly IReadOnlyDictionary<int, LazyType2Command> SingleByteCommandStore = new Dictionary<int, LazyType2Command>
{
{ 1, new LazyType2Command("hstem", x => { })},
{ 3, new LazyType2Command("vstem", x => { })},
{ 4, new LazyType2Command("vmoveto", x => { })},
{ 5, new LazyType2Command("rlineto", x => { })},
{ 6, new LazyType2Command("hlineto", x => { })},
{ 7, new LazyType2Command("vlineto", x => { })},
{ 8, new LazyType2Command("rrcurveto", x => { })},
{ 10, new LazyType2Command("callsubr", x => { })},
{ 11, new LazyType2Command("return", x => { })},
{ 14, new LazyType2Command("endchar", x => { })},
{ 18, new LazyType2Command("hstemhm", x => { })},
{ 19, new LazyType2Command("hintmask", x => { })},
{ 20, new LazyType2Command("cntrmask", x => { })},
{ 21, new LazyType2Command("rmoveto", x => { })},
{ 22, new LazyType2Command("hmoveto", x => { })},
{ 23, new LazyType2Command("vstemhm", x => { })},
{ 24, new LazyType2Command("rcurveline", x => { })},
{ 25, new LazyType2Command("rlinecurve", x => { })},
{ 26, new LazyType2Command("vvcurveto", x => { })},
{ 27, new LazyType2Command("hhcurveto", x => { })},
{ 29, new LazyType2Command("callgsubr", x => { })},
{ 30, new LazyType2Command("vhcurveto", x => { })},
{ 31, new LazyType2Command("hvcurveto", x => { })}
};
private static readonly IReadOnlyDictionary<int, LazyType2Command> TwoByteCommandStore = new Dictionary<int, LazyType2Command>
{
{ 3, new LazyType2Command("and", x => { })},
{ 4, new LazyType2Command("or", x => { })},
{ 5, new LazyType2Command("not", x => { })},
{ 9, new LazyType2Command("abs", x => { })},
{ 10, new LazyType2Command("add", x => { })},
{ 11, new LazyType2Command("sub", x => { })},
{ 12, new LazyType2Command("div", x => { })},
{ 14, new LazyType2Command("neg", x => { })},
{ 15, new LazyType2Command("eq", x => { })},
{ 18, new LazyType2Command("drop", x => { })},
{ 20, new LazyType2Command("put", x => { })},
{ 21, new LazyType2Command("get", x => { })},
{ 22, new LazyType2Command("ifelse", x => { })},
{ 23, new LazyType2Command("random", x => { })},
{ 24, new LazyType2Command("mul", x => { })},
{ 26, new LazyType2Command("sqrt", x => { })},
{ 27, new LazyType2Command("dup", x => { })},
{ 28, new LazyType2Command("exch", x => { })},
{ 29, new LazyType2Command("index", x => { })},
{ 30, new LazyType2Command("roll", x => { })},
{ 34, new LazyType2Command("hflex", x => { })},
{ 35, new LazyType2Command("flex", x => { })},
{ 36, new LazyType2Command("hflex1", x => { })},
{ 37, new LazyType2Command("flex1", x => { })},
};
private static LazyType2Command GetCommand(byte b, IReadOnlyList<byte> bytes, ref int i)
{
if (b == 12)
@@ -150,7 +762,7 @@
return commandTwoByte;
}
return new LazyType2Command($"unknown: {b} {b2}", x => {});
return new LazyType2Command($"unknown: {b} {b2}", x => { });
}
if (SingleByteCommandStore.TryGetValue(b, out var command))
@@ -158,7 +770,7 @@
return command;
}
return new LazyType2Command($"unknown: {b}", x => {});
return new LazyType2Command($"unknown: {b}", x => { });
}
}
}

View File

@@ -0,0 +1,45 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System;
using System.Collections.Generic;
internal abstract class CompactFontFormatCharset : ICompactFontFormatCharset
{
protected readonly IReadOnlyDictionary<int, (int stringId, string name)> GlyphIdToStringIdAndName;
protected CompactFontFormatCharset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var dictionary = new Dictionary<int, (int stringId, string name)>
{
{0, (0, ".notdef")}
};
foreach (var tuple in data)
{
dictionary[tuple.glyphId] = (tuple.stringId, tuple.name);
}
GlyphIdToStringIdAndName = dictionary;
}
public virtual string GetNameByGlyphId(int glyphId)
{
throw new NotImplementedException();
}
public virtual string GetNameByStringId(int stringId)
{
throw new NotImplementedException();
}
public virtual string GetStringIdByGlyphId(int glyphId)
{
throw new NotImplementedException();
}
}
}

View File

@@ -5,7 +5,7 @@
/// <summary>
/// A predefined Charset for a Compact Font Format font with Charset Id of 1.
/// </summary>
internal class CompactFontFormatExpertCharset
internal class CompactFontFormatExpertCharset : ICompactFontFormatCharset
{
private static readonly IReadOnlyDictionary<int, string> StringIdToName = new Dictionary<int, string>
{
@@ -192,5 +192,20 @@
characterIdToStringIdAndName = furtherMap;
}
public string GetNameByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
public string GetNameByStringId(int stringId)
{
throw new System.NotImplementedException();
}
public string GetStringIdByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -5,7 +5,7 @@
/// <summary>
/// A predefined Charset for a Compact Font Format font with Charset Id of 2.
/// </summary>
internal class CompactFontFormatExpertSubsetCharset
internal class CompactFontFormatExpertSubsetCharset : ICompactFontFormatCharset
{
private static readonly IReadOnlyDictionary<int, string> StringIdToName = new Dictionary<int, string>
{
@@ -113,5 +113,20 @@
characterIdToStringIdAndName = furtherMap;
}
public string GetNameByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
public string GetNameByStringId(int stringId)
{
throw new System.NotImplementedException();
}
public string GetStringIdByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -1,89 +1,15 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System;
using System.Collections.Generic;
/// <summary>
/// A Charset from a Compact Font Format font file best for fonts with relatively unordered string ids.
/// </summary>
internal class CompactFontFormatFormat0Charset
internal class CompactFontFormatFormat0Charset : CompactFontFormatCharset
{
private readonly IReadOnlyDictionary<int, (int stringId, string name)> glyphIdToStringIdAndName;
public CompactFontFormatFormat0Charset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
:base(data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var dictionary = new Dictionary<int, (int stringId, string name)>
{
{0, (0, ".notdef")}
};
foreach (var tuple in data)
{
dictionary[tuple.glyphId] = (tuple.stringId, tuple.name);
}
glyphIdToStringIdAndName = dictionary;
}
}
/// <summary>
/// A Charset from a Compact Font Format font file best for fonts with well ordered string ids.
/// </summary>
internal class CompactFontFormatFormat1Charset
{
private readonly IReadOnlyDictionary<int, (int stringId, string name)> glyphIdToStringIdAndName;
public CompactFontFormatFormat1Charset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var dictionary = new Dictionary<int, (int stringId, string name)>
{
{0, (0, ".notdef")}
};
foreach (var tuple in data)
{
dictionary[tuple.glyphId] = (tuple.stringId, tuple.name);
}
glyphIdToStringIdAndName = dictionary;
}
}
/// <summary>
/// A Charset from a Compact Font Format font file best for fonts with a large number of well ordered string ids.
/// </summary>
internal class CompactFontFormatFormat2Charset
{
private readonly IReadOnlyDictionary<int, (int stringId, string name)> glyphIdToStringIdAndName;
public CompactFontFormatFormat2Charset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}
var dictionary = new Dictionary<int, (int stringId, string name)>
{
{0, (0, ".notdef")}
};
foreach (var tuple in data)
{
dictionary[tuple.glyphId] = (tuple.stringId, tuple.name);
}
glyphIdToStringIdAndName = dictionary;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System.Collections.Generic;
/// <summary>
/// A Charset from a Compact Font Format font file best for fonts with well ordered string ids.
/// </summary>
internal class CompactFontFormatFormat1Charset : CompactFontFormatCharset
{
public CompactFontFormatFormat1Charset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
: base(data)
{
}
}
}

View File

@@ -0,0 +1,15 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
using System.Collections.Generic;
/// <summary>
/// A Charset from a Compact Font Format font file best for fonts with a large number of well ordered string ids.
/// </summary>
internal class CompactFontFormatFormat2Charset : CompactFontFormatCharset
{
public CompactFontFormatFormat2Charset(IReadOnlyList<(int glyphId, int stringId, string name)> data)
: base(data)
{
}
}
}

View File

@@ -5,7 +5,7 @@
/// <summary>
/// A predefined Charset for a Compact Font Format font with Charset Id of 0.
/// </summary>
internal class CompactFontFormatIsoAdobeCharset
internal class CompactFontFormatIsoAdobeCharset : ICompactFontFormatCharset
{
private static readonly IReadOnlyDictionary<int, string> StringIdToName = new Dictionary<int, string>
{
@@ -255,5 +255,20 @@
characterIdToStringIdAndName = furtherMap;
}
public string GetNameByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
public string GetNameByStringId(int stringId)
{
throw new System.NotImplementedException();
}
public string GetStringIdByGlyphId(int glyphId)
{
throw new System.NotImplementedException();
}
}
}

View File

@@ -0,0 +1,11 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.Charsets
{
internal interface ICompactFontFormatCharset
{
string GetNameByGlyphId(int glyphId);
string GetNameByStringId(int stringId);
string GetStringIdByGlyphId(int glyphId);
}
}

View File

@@ -0,0 +1,26 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
{
using Charsets;
using CharStrings;
using Dictionaries;
using Type1.CharStrings;
using Util;
internal class CompactFontFormatFont
{
private readonly CompactFontFormatTopLevelDictionary topDictionary;
private readonly CompactFontFormatPrivateDictionary privateDictionary;
private readonly ICompactFontFormatCharset charset;
private readonly Union<Type1CharStrings, Type2CharStrings> charStrings;
public CompactFontFormatFont(CompactFontFormatTopLevelDictionary topDictionary, CompactFontFormatPrivateDictionary privateDictionary,
ICompactFontFormatCharset charset,
Union<Type1CharStrings, Type2CharStrings> charStrings)
{
this.topDictionary = topDictionary;
this.privateDictionary = privateDictionary;
this.charset = charset;
this.charStrings = charStrings;
}
}
}

View File

@@ -0,0 +1,34 @@
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
{
using System;
using System.Collections;
using System.Collections.Generic;
using Util.JetBrains.Annotations;
internal class CompactFontFormatIndex : IReadOnlyList<IReadOnlyList<byte>>
{
[CanBeNull]
private readonly IReadOnlyList<IReadOnlyList<byte>> bytes;
public int Count => bytes.Count;
public IReadOnlyList<byte> this[int index] => bytes[index];
public static CompactFontFormatIndex None { get; } = new CompactFontFormatIndex(new byte[0][]);
public CompactFontFormatIndex(byte[][] bytes)
{
this.bytes = bytes ?? Array.Empty<IReadOnlyList<byte>>();
}
public IEnumerator<IReadOnlyList<byte>> GetEnumerator()
{
return bytes.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@@ -4,7 +4,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat
{
internal class CompactFontFormatIndexReader
{
public byte[][] ReadDictionaryData(CompactFontFormatData data)
public CompactFontFormatIndex ReadDictionaryData(CompactFontFormatData data)
{
var index = ReadIndex(data);
@@ -24,7 +24,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat
results[i] = data.ReadBytes(length);
}
return results;
return new CompactFontFormatIndex(results);
}
public int[] ReadIndex(CompactFontFormatData data)

View File

@@ -2,9 +2,12 @@
{
using System;
using System.Collections.Generic;
using System.Linq;
using Charsets;
using CharStrings;
using Dictionaries;
using Type1.CharStrings;
using Util;
internal class CompactFontFormatIndividualFontParser
{
@@ -21,9 +24,10 @@
this.privateDictionaryReader = privateDictionaryReader;
}
public void Parse(CompactFontFormatData data, string name, byte[] topDictionaryIndex, string[] stringIndex)
public CompactFontFormatFont Parse(CompactFontFormatData data, string name, IReadOnlyList<byte> topDictionaryIndex, IReadOnlyList<string> stringIndex,
CompactFontFormatIndex globalSubroutineIndex)
{
var individualData = new CompactFontFormatData(topDictionaryIndex);
var individualData = new CompactFontFormatData(topDictionaryIndex.ToArray());
var topDictionary = topLevelDictionaryReader.Read(individualData, stringIndex);
@@ -41,11 +45,19 @@
throw new InvalidOperationException("Expected CFF to contain a CharString offset.");
}
var localSubroutines = CompactFontFormatIndex.None;
if (privateDictionary.LocalSubroutineLocalOffset.HasValue)
{
data.Seek(privateDictionary.LocalSubroutineLocalOffset.Value);
localSubroutines = indexReader.ReadDictionaryData(data);
}
data.Seek(topDictionary.CharStringsOffset);
var charStringIndex = indexReader.ReadDictionaryData(data);
object charset = null;
ICompactFontFormatCharset charset = null;
if (topDictionary.IsCidFont && topDictionary.CharSetOffset >= 0 && topDictionary.CharSetOffset <= 2)
{
@@ -74,7 +86,7 @@
{
var glyphToNamesAndStringId = new List<(int glyphId, int stringId, string name)>();
for (var glyphId = 1; glyphId < charStringIndex.Length; glyphId++)
for (var glyphId = 1; glyphId < charStringIndex.Count; glyphId++)
{
var stringId = data.ReadSid();
glyphToNamesAndStringId.Add((glyphId, stringId, ReadString(stringId, stringIndex)));
@@ -89,7 +101,7 @@
{
var glyphToNamesAndStringId = new List<(int glyphId, int stringId, string name)>();
for (var glyphId = 1; glyphId < charStringIndex.Length; glyphId++)
for (var glyphId = 1; glyphId < charStringIndex.Count; glyphId++)
{
var firstSid = data.ReadSid();
var numberInRange = format == 1 ? data.ReadCard8() : data.ReadCard16();
@@ -98,7 +110,8 @@
glyphId++;
for (var i = 0; i < numberInRange; i++)
{
glyphToNamesAndStringId.Add((glyphId, firstSid + i + 1, ReadString(firstSid, stringIndex)));
var sid = firstSid + i + 1;
glyphToNamesAndStringId.Add((glyphId, sid, ReadString(sid, stringIndex)));
glyphId++;
}
}
@@ -128,25 +141,27 @@
case CompactFontFormatCharStringType.Type1:
throw new NotImplementedException();
case CompactFontFormatCharStringType.Type2:
charStrings = Type2CharStringParser.Parse(charStringIndex);
charStrings = Type2CharStringParser.Parse(charStringIndex, localSubroutines, globalSubroutineIndex);
break;
default:
throw new ArgumentOutOfRangeException($"Unexpected CharString type in CFF font: {topDictionary.CharStringType}.");
}
return new CompactFontFormatFont(topDictionary, privateDictionary, charset, Union<Type1CharStrings, Type2CharStrings>.Two(charStrings));
}
private static string ReadString(int index, string[] stringIndex)
private static string ReadString(int index, IReadOnlyList<string> stringIndex)
{
if (index >= 0 && index <= 390)
{
return CompactFontFormatStandardStrings.GetName(index);
}
if (index - 391 < stringIndex.Length)
if (index - 391 < stringIndex.Count)
{
return stringIndex[index - 391];
}
// technically this maps to .notdef, but we need a unique sid name
// technically this maps to .notdef, but we PDFBox uses this
return "SID" + index;
}
}

View File

@@ -25,7 +25,7 @@
switch (tag)
{
case TagOtto:
throw new NotImplementedException("Currently tagged CFF data is not supported.");
throw new NotSupportedException("Currently tagged CFF data is not supported.");
case TagTtcf:
throw new NotSupportedException("True Type Collection fonts are not supported.");
case TagTtfonly:
@@ -49,7 +49,7 @@
{
var fontName = fontNames[i];
individualFontParser.Parse(data, fontName, topLevelDict[i], stringIndex);
individualFontParser.Parse(data, fontName, topLevelDict[i], stringIndex, globalSubroutineIndex);
}
}

View File

@@ -9,9 +9,9 @@
{
private readonly List<Operand> operands = new List<Operand>();
public abstract T Read(CompactFontFormatData data, string[] stringIndex);
public abstract T Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex);
protected T ReadDictionary(T dictionary, CompactFontFormatData data, string[] stringIndex)
protected T ReadDictionary(T dictionary, CompactFontFormatData data, IReadOnlyList<string> stringIndex)
{
while (data.CanRead())
{
@@ -162,9 +162,9 @@
return decimal.Parse(sb.ToString());
}
protected abstract void ApplyOperation(T dictionary, List<Operand> operands, OperandKey operandKey, string[] stringIndex);
protected abstract void ApplyOperation(T dictionary, List<Operand> operands, OperandKey operandKey, IReadOnlyList<string> stringIndex);
protected static string GetString(List<Operand> operands, string[] stringIndex)
protected static string GetString(List<Operand> operands, IReadOnlyList<string> stringIndex)
{
if (operands.Count == 0)
{
@@ -184,7 +184,7 @@
}
var stringIndexIndex = index - 391;
if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Length)
if (stringIndexIndex >= 0 && stringIndexIndex < stringIndex.Count)
{
return stringIndex[stringIndexIndex];
}

View File

@@ -32,7 +32,7 @@
public decimal InitialRandomSeed { get; set; }
public int LocalSubroutineLocalOffset { get; set; }
public int? LocalSubroutineLocalOffset { get; set; }
/// <summary>
/// If a glyph's width equals the default width X it can be omitted from the charstring.

View File

@@ -5,7 +5,7 @@
internal class CompactFontFormatPrivateDictionaryReader : CompactFontFormatDictionaryReader<CompactFontFormatPrivateDictionary>
{
public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, string[] stringIndex)
public override CompactFontFormatPrivateDictionary Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex)
{
var dictionary = new CompactFontFormatPrivateDictionary();
@@ -14,7 +14,7 @@
return dictionary;
}
protected override void ApplyOperation(CompactFontFormatPrivateDictionary dictionary, List<Operand> operands, OperandKey operandKey, string[] stringIndex)
protected override void ApplyOperation(CompactFontFormatPrivateDictionary dictionary, List<Operand> operands, OperandKey operandKey, IReadOnlyList<string> stringIndex)
{
switch (operandKey.Byte0)
{

View File

@@ -6,7 +6,7 @@
internal class CompactFontFormatTopLevelDictionaryReader : CompactFontFormatDictionaryReader<CompactFontFormatTopLevelDictionary>
{
public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, string[] stringIndex)
public override CompactFontFormatTopLevelDictionary Read(CompactFontFormatData data, IReadOnlyList<string> stringIndex)
{
var dictionary = new CompactFontFormatTopLevelDictionary();
@@ -15,7 +15,7 @@
return dictionary;
}
protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List<Operand> operands, OperandKey key, string[] stringIndex)
protected override void ApplyOperation(CompactFontFormatTopLevelDictionary dictionary, List<Operand> operands, OperandKey key, IReadOnlyList<string> stringIndex)
{
switch (key.Byte0)
{

View File

@@ -52,6 +52,16 @@
Y = (decimal)y;
}
public PdfPoint MoveX(decimal dx)
{
return new PdfPoint(X + dx, Y);
}
public PdfPoint MoveY(decimal dy)
{
return new PdfPoint(X, Y + dy);
}
internal PdfVector ToVector()
{
return new PdfVector(X, Y);