mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
add charset interface, create class to store cff font data. add the command logic for type 2 charstrings #6
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user