diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs index b7fb85ed..2505f4cf 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/CallOtherSubrCommand.cs @@ -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; /// /// 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(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."); diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs index a6679225..f49634e3 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Arithmetic/SetCurrentPointCommand.cs @@ -4,6 +4,8 @@ /// /// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command. + /// 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. /// 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(); } } diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs index 9e4567cd..0c415f81 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/HMoveToCommand.cs @@ -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(); diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs index 1aa75dfa..8d4efcd0 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/RMoveToCommand.cs @@ -30,7 +30,7 @@ if (context.IsFlexing) { - + context.AddFlexPoint(new PdfPoint(deltaX, deltaY)); } else { diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs index 0099e208..f7a9beb6 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/PathConstruction/VMoveToCommand.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction { using Core; + using System; /// /// 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 { diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs index 93e749fe..e6b7a194 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/StartFinishOutline/SbwCommand.cs @@ -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. /// - internal class SbwCommand + internal static class SbwCommand { public const string Name = "sbw"; diff --git a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Type1BuildCharContext.cs b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Type1BuildCharContext.cs index 603a63c9..ff422bd4 100644 --- a/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Type1BuildCharContext.cs +++ b/src/UglyToad.PdfPig.Fonts/Type1/CharStrings/Commands/Type1BuildCharContext.cs @@ -28,7 +28,7 @@ public CharStringStack PostscriptStack { get; } = new CharStringStack(); - public IReadOnlyList FlexPoints { get; } + public List FlexPoints { get; } = new List(); public Type1BuildCharContext(IReadOnlyDictionary subroutines, Func 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(); } } } diff --git a/src/UglyToad.PdfPig.Tests/Fonts/Type1/Type1CharStringParserTests.cs b/src/UglyToad.PdfPig.Tests/Fonts/Type1/Type1CharStringParserTests.cs new file mode 100644 index 00000000..19b013e8 --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Fonts/Type1/Type1CharStringParserTests.cs @@ -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); + } + } + } +} diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/data.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/data.pdf new file mode 100644 index 00000000..8bdda7f4 Binary files /dev/null and b/src/UglyToad.PdfPig.Tests/Integration/Documents/data.pdf differ diff --git a/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs index 3b3dd813..10c7b9fa 100644 --- a/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs @@ -8,7 +8,7 @@ internal class NumericTokenizer : ITokenizer { - private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10); + private readonly StringBuilder stringBuilder = new StringBuilder(); private const byte Zero = 48; private const byte Nine = 57; @@ -23,7 +23,7 @@ if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.') { - characters = StringBuilderPool.Borrow(); + characters = stringBuilder; characters.Append((char)currentByte); } else @@ -53,7 +53,7 @@ try { var str = characters.ToString(); - StringBuilderPool.Return(characters); + characters.Clear(); switch (str) { diff --git a/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs index 8ef87512..20afccb4 100644 --- a/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs @@ -1,11 +1,12 @@ namespace UglyToad.PdfPig.Tokenization { using Core; + using System.Text; using Tokens; internal class PlainTokenizer : ITokenizer { - private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10); + private readonly StringBuilder stringBuilder = new StringBuilder(); public bool ReadsNextByte { get; } = true; @@ -18,7 +19,7 @@ return false; } - var builder = StringBuilderPool.Borrow(); + var builder = stringBuilder; builder.Append((char)currentByte); while (inputBytes.MoveNext()) { @@ -39,7 +40,7 @@ } var text = builder.ToString(); - StringBuilderPool.Return(builder); + builder.Clear(); switch (text) { diff --git a/src/UglyToad.PdfPig.Tokenization/Scanner/CoreTokenScanner.cs b/src/UglyToad.PdfPig.Tokenization/Scanner/CoreTokenScanner.cs index 22f68b34..2f676076 100644 --- a/src/UglyToad.PdfPig.Tokenization/Scanner/CoreTokenScanner.cs +++ b/src/UglyToad.PdfPig.Tokenization/Scanner/CoreTokenScanner.cs @@ -15,9 +15,12 @@ private static readonly DictionaryTokenizer DictionaryTokenizer = new DictionaryTokenizer(); private static readonly HexTokenizer HexTokenizer = new HexTokenizer(); private static readonly NameTokenizer NameTokenizer = new NameTokenizer(); - private static readonly NumericTokenizer NumericTokenizer = new NumericTokenizer(); - private static readonly PlainTokenizer PlainTokenizer = new PlainTokenizer(); - private static readonly StringTokenizer StringTokenizer = new StringTokenizer(); + + // NOTE: these are not thread safe so should not be static. Each instance includes a + // StringBuilder it re-uses. + private readonly PlainTokenizer PlainTokenizer = new PlainTokenizer(); + private readonly NumericTokenizer NumericTokenizer = new NumericTokenizer(); + private readonly StringTokenizer StringTokenizer = new StringTokenizer(); private readonly ScannerScope scope; private readonly IInputBytes inputBytes; diff --git a/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs index bc68e919..6592dddd 100644 --- a/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs @@ -6,7 +6,7 @@ internal class StringTokenizer : ITokenizer { - private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(16); + private readonly StringBuilder stringBuilder = new StringBuilder(); public bool ReadsNextByte { get; } = false; public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token) @@ -23,7 +23,7 @@ return false; } - var builder = StringBuilderPool.Borrow(); + var builder = stringBuilder; var numberOfBrackets = 1; var isEscapeActive = false; var isLineBreaking = false; @@ -178,7 +178,7 @@ encodedWith = StringToken.Encoding.Iso88591; } - StringBuilderPool.Return(builder); + builder.Clear(); token = new StringToken(tokenStr, encodedWith); diff --git a/src/UglyToad.PdfPig.Tokenization/UglyToad.PdfPig.Tokenization.csproj b/src/UglyToad.PdfPig.Tokenization/UglyToad.PdfPig.Tokenization.csproj index a8effc40..09fa1ff5 100644 --- a/src/UglyToad.PdfPig.Tokenization/UglyToad.PdfPig.Tokenization.csproj +++ b/src/UglyToad.PdfPig.Tokenization/UglyToad.PdfPig.Tokenization.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;net45;net451;net452;net46;net461;net462;net47 latest