Merge pull request #240 from BobLd/type1-flex

Correct letter bbox for Type1 font/flexpoints - Fix #217
This commit is contained in:
Eliot Jones 2021-01-20 08:26:51 -04:00 committed by GitHub
commit 1d36098ec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 100 additions and 28 deletions

View File

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UglyToad.PdfPig.Core;
using UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction;
/// <summary>
/// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then
@ -23,13 +25,13 @@
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static void Run(Type1BuildCharContext context)
{
var index = (int) context.Stack.PopTop();
var index = (int)context.Stack.PopTop();
// What it should do
var numberOfArguments = (int)context.Stack.PopTop();
var otherSubroutineArguments = new List<double>(numberOfArguments);
@ -42,17 +44,44 @@
{
// Other subrs 0-2 implement flex
case FlexEnd:
{
context.IsFlexing = false;
// TODO: I don't really care about flexpoints, but we should probably handle them... one day.
//if (context.FlexPoints.Count < 7)
//{
// throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
//}
{
// https://github.com/apache/pdfbox/blob/2c23d8b4e3ad61852f0b6ee2b95b907eefba1fcf/fontbox/src/main/java/org/apache/fontbox/cff/Type1CharString.java#L339
context.IsFlexing = false;
if (context.FlexPoints.Count < 7)
{
throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
}
context.ClearFlexPoints();
break;
}
// reference point is relative to start point
PdfPoint reference = context.FlexPoints[0];
reference = reference.Translate(context.CurrentPosition.X, context.CurrentPosition.Y);
// first point is relative to reference point
PdfPoint first = context.FlexPoints[1];
first = first.Translate(reference.X, reference.Y);
// make the first point relative to the start point
first = first.Translate(-context.CurrentPosition.X, -context.CurrentPosition.Y);
context.Stack.Push(first.X);
context.Stack.Push(first.Y);
context.Stack.Push(context.FlexPoints[2].X);
context.Stack.Push(context.FlexPoints[2].Y);
context.Stack.Push(context.FlexPoints[3].X);
context.Stack.Push(context.FlexPoints[3].Y);
RelativeRCurveToCommand.Run(context);
context.Stack.Push(context.FlexPoints[4].X);
context.Stack.Push(context.FlexPoints[4].Y);
context.Stack.Push(context.FlexPoints[5].X);
context.Stack.Push(context.FlexPoints[5].Y);
context.Stack.Push(context.FlexPoints[6].X);
context.Stack.Push(context.FlexPoints[6].Y);
RelativeRCurveToCommand.Run(context);
context.ClearFlexPoints();
break;
}
case FlexBegin:
Debug.Assert(otherSubroutineArguments.Count == 0, "Flex begin should have no arguments.");

View File

@ -4,6 +4,8 @@
/// <summary>
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
/// <para>This establishes the current point for a subsequent relative path building command.
/// The 'setcurrentpoint' command is used only in conjunction with results from 'OtherSubrs' procedures.</para>
/// </summary>
internal static class SetCurrentPointCommand
{
@ -22,8 +24,8 @@
var x = context.Stack.PopBottom();
var y = context.Stack.PopBottom();
context.CurrentPosition = new PdfPoint(x, y);
//context.CurrentPosition = new PdfPoint(x, y);
// TODO: need to investigate why odd behavior when the current point is actualy set.
context.Stack.Clear();
}
}

View File

@ -19,19 +19,19 @@
public static void Run(Type1BuildCharContext context)
{
var x = context.Stack.PopBottom();
var actualX = context.CurrentPosition.X + x;
var y = context.CurrentPosition.Y;
var deltaX = context.Stack.PopBottom();
if (context.IsFlexing)
{
// TODO: flex support
// not in the Type 1 spec, but exists in some fonts
context.AddFlexPoint(new PdfPoint(deltaX, 0));
}
else
{
context.CurrentPosition = new PdfPoint(actualX, y);
context.Path.MoveTo(actualX, y);
var x = context.CurrentPosition.X + deltaX;
var y = context.CurrentPosition.Y;
context.CurrentPosition = new PdfPoint(x, y);
context.Path.MoveTo(x, y);
}
context.Stack.Clear();

View File

@ -30,7 +30,7 @@
if (context.IsFlexing)
{
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
}
else
{

View File

@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
using Core;
using System;
/// <summary>
/// Vertical move to. Moves relative to the current point.
@ -23,7 +24,8 @@
if (context.IsFlexing)
{
// TODO: flex commands
// not in the Type 1 spec, but exists in some fonts
context.AddFlexPoint(new PdfPoint(0, deltaY));
}
else
{

View File

@ -6,7 +6,7 @@
/// Sets left sidebearing and the character width vector.
/// This command also sets the current point to(sbx, sby), but does not place the point in the character path.
/// </summary>
internal class SbwCommand
internal static class SbwCommand
{
public const string Name = "sbw";

View File

@ -28,7 +28,7 @@
public CharStringStack PostscriptStack { get; } = new CharStringStack();
public IReadOnlyList<PdfPoint> FlexPoints { get; }
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
Func<int, PdfSubpath> characterByIndexFactory,
@ -41,7 +41,7 @@
public void AddFlexPoint(PdfPoint point)
{
FlexPoints.Add(point);
}
public PdfSubpath GetCharacter(int characterCode)
@ -61,7 +61,7 @@
public void ClearFlexPoints()
{
FlexPoints.Clear();
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.IO;
using UglyToad.PdfPig.Core;
using Xunit;
namespace UglyToad.PdfPig.Tests.Fonts.Type1
{
public class Type1CharStringParserTests
{
[Fact]
public void CorrectBoundingBoxesFlexPoints()
{
PointComparer pointComparer = new PointComparer(new DoubleComparer(3));
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
var filePath = Path.Combine(documentFolder, "data.pdf");
using (var doc = PdfDocument.Open(filePath))
{
var page = doc.GetPage(1);
var letters = page.Letters;
// check 'm'
var m = letters[0];
Assert.Equal("m", m.Value);
Assert.Equal(new PdfPoint(253.4458, 658.431), m.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.GlyphRectangle.TopRight, pointComparer);
// check 'p'
var p = letters[1];
Assert.Equal("p", p.Value);
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.GlyphRectangle.TopRight, pointComparer);
}
}
}
}