mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-04-05 20:55:01 +08:00
Implement TryGetPath and TryGetNormalisedPath for fonts
This commit is contained in:
parent
5eebe9d0f9
commit
cc6e2d302f
@ -196,7 +196,7 @@
|
||||
throw new ArgumentNullException("BezierCurveTo(): currentPosition is null.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Close the path.
|
||||
/// </summary>
|
||||
@ -212,7 +212,7 @@
|
||||
}
|
||||
commands.Add(new Close());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the path is currently closed.
|
||||
/// </summary>
|
||||
@ -350,6 +350,30 @@
|
||||
return new PdfRectangle(mv.Location, new PdfPoint(mv.Location.X + width, mv.Location.Y + height));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
|
||||
/// </summary>
|
||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
public static PdfRectangle? GetBoundingRectangle(IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (path == null || path.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bboxes = path.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x.Value).ToList();
|
||||
if (bboxes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var minX = bboxes.Min(x => x.Left);
|
||||
var minY = bboxes.Min(x => x.Bottom);
|
||||
var maxX = bboxes.Max(x => x.Right);
|
||||
var maxY = bboxes.Max(x => x.Top);
|
||||
return new PdfRectangle(minX, minY, maxX, maxY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A command in a <see cref="PdfSubpath"/>.
|
||||
/// </summary>
|
||||
|
@ -1,8 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using static UglyToad.PdfPig.Core.PdfSubpath;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
|
||||
@ -256,6 +259,59 @@
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform a subpath using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="subpath">The original subpath.</param>
|
||||
/// <returns>A new subpath which is the result of applying this transformation matrix.</returns>
|
||||
public PdfSubpath Transform(PdfSubpath subpath)
|
||||
{
|
||||
var trSubpath = new PdfSubpath();
|
||||
foreach (var c in subpath.Commands)
|
||||
{
|
||||
if (c is Move move)
|
||||
{
|
||||
var loc = Transform(move.Location);
|
||||
trSubpath.MoveTo(loc.X, loc.Y);
|
||||
}
|
||||
else if (c is Line line)
|
||||
{
|
||||
//var from = Transform(line.From);
|
||||
var to = Transform(line.To);
|
||||
trSubpath.LineTo(to.X, to.Y);
|
||||
}
|
||||
else if (c is BezierCurve curve)
|
||||
{
|
||||
var first = Transform(curve.FirstControlPoint);
|
||||
var second = Transform(curve.SecondControlPoint);
|
||||
var end = Transform(curve.EndPoint);
|
||||
trSubpath.BezierCurveTo(first.X, first.Y, second.X, second.Y, end.X, end.Y);
|
||||
}
|
||||
else if (c is Close)
|
||||
{
|
||||
trSubpath.CloseSubpath();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unknown PdfSubpath type");
|
||||
}
|
||||
}
|
||||
return trSubpath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform a path using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="path">The original path.</param>
|
||||
/// <returns>A new path which is the result of applying this transformation matrix.</returns>
|
||||
public IEnumerable<PdfSubpath> Transform(IEnumerable<PdfSubpath> path)
|
||||
{
|
||||
foreach (var subpath in path)
|
||||
{
|
||||
yield return Transform(subpath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a <see cref="TransformationMatrix"/> translated by the specified amount.
|
||||
/// </summary>
|
||||
|
@ -1,7 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// The context used and updated when interpreting the commands for a charstring.
|
||||
@ -18,7 +18,7 @@
|
||||
/// <summary>
|
||||
/// The current path.
|
||||
/// </summary>
|
||||
public PdfSubpath Path { get; } = new PdfSubpath();
|
||||
public List<PdfSubpath> Path { get; } = new List<PdfSubpath>();
|
||||
|
||||
/// <summary>
|
||||
/// The current location of the active point.
|
||||
@ -41,6 +41,28 @@
|
||||
AddRelativeLine(0, dy);
|
||||
}
|
||||
|
||||
public void AddRelativeMoveTo(double dx, double dy)
|
||||
{
|
||||
BeforeMoveTo();
|
||||
var newLocation = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
|
||||
Path[Path.Count - 1].MoveTo(newLocation.X, newLocation.Y);
|
||||
CurrentLocation = newLocation;
|
||||
}
|
||||
|
||||
public void AddHorizontalMoveTo(double dx)
|
||||
{
|
||||
BeforeMoveTo();
|
||||
Path[Path.Count - 1].MoveTo(CurrentLocation.X + dx, CurrentLocation.Y);
|
||||
CurrentLocation = CurrentLocation.MoveX(dx);
|
||||
}
|
||||
|
||||
public void AddVerticallMoveTo(double dy)
|
||||
{
|
||||
BeforeMoveTo();
|
||||
Path[Path.Count - 1].MoveTo(CurrentLocation.X, CurrentLocation.Y + dy);
|
||||
CurrentLocation = CurrentLocation.MoveY(dy);
|
||||
}
|
||||
|
||||
public void AddRelativeBezierCurve(double dx1, double dy1, double dx2, double dy2, double dx3, double dy3)
|
||||
{
|
||||
var x1 = CurrentLocation.X + dx1;
|
||||
@ -52,7 +74,7 @@
|
||||
var x3 = x2 + dx3;
|
||||
var y3 = y2 + dy3;
|
||||
|
||||
Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
Path[Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
CurrentLocation = new PdfPoint(x3, y3);
|
||||
}
|
||||
|
||||
@ -60,7 +82,7 @@
|
||||
{
|
||||
var dest = new PdfPoint(CurrentLocation.X + dx, CurrentLocation.Y + dy);
|
||||
|
||||
Path.LineTo(dest.X, dest.Y);
|
||||
Path[Path.Count - 1].LineTo(dest.X, dest.Y);
|
||||
CurrentLocation = dest;
|
||||
}
|
||||
|
||||
@ -77,6 +99,23 @@
|
||||
transientArray[location] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Every character path and subpath must begin with one of the
|
||||
/// moveto operators. If the current path is open when a moveto
|
||||
/// operator is encountered, the path is closed before performing
|
||||
/// the moveto operation.
|
||||
/// <para>See 4.1 Path Construction Operators in 'The Type 2 Charstring Format, Technical Note #5177', 16 March 2000</para>
|
||||
/// <see href="https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf"/>
|
||||
/// </summary>
|
||||
private void BeforeMoveTo()
|
||||
{
|
||||
if (Path.Count > 0)
|
||||
{
|
||||
Path[Path.Count - 1].CloseSubpath();
|
||||
}
|
||||
Path.Add(new PdfSubpath());
|
||||
}
|
||||
|
||||
public double GetFromTransientArray(int location)
|
||||
{
|
||||
var result = transientArray[location];
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
{
|
||||
using Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Core;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the decoded command sequences for Type 2 CharStrings from a Compact Font Format font as well
|
||||
@ -21,7 +21,6 @@
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
||||
|
||||
|
||||
public Type2CharStrings(IReadOnlyDictionary<string, CommandSequence> charStrings)
|
||||
{
|
||||
CharStrings = charStrings ?? throw new ArgumentNullException(nameof(charStrings));
|
||||
@ -70,7 +69,7 @@
|
||||
private static Type2Glyph Run(CommandSequence sequence, double defaultWidthX, double nominalWidthX)
|
||||
{
|
||||
var context = new Type2BuildCharContext();
|
||||
|
||||
|
||||
var hasRunStackClearingCommand = false;
|
||||
for (var i = -1; i < sequence.Values.Count; i++)
|
||||
{
|
||||
@ -224,7 +223,7 @@
|
||||
/// <summary>
|
||||
/// The path of the glyph.
|
||||
/// </summary>
|
||||
public PdfSubpath Path { get; }
|
||||
public IReadOnlyList<PdfSubpath> Path { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The width of the glyph as a difference from the nominal width X for the font. Optional.
|
||||
@ -234,7 +233,7 @@
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Type2Glyph"/>.
|
||||
/// </summary>
|
||||
public Type2Glyph(PdfSubpath path, double? width)
|
||||
public Type2Glyph(IReadOnlyList<PdfSubpath> path, double? width)
|
||||
{
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Width = width;
|
||||
|
@ -1,12 +1,12 @@
|
||||
namespace UglyToad.PdfPig.Fonts.CompactFontFormat
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Charsets;
|
||||
using CharStrings;
|
||||
using Core;
|
||||
using Dictionaries;
|
||||
using Encodings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Type1.CharStrings;
|
||||
|
||||
/// <summary>
|
||||
@ -69,7 +69,7 @@
|
||||
}
|
||||
|
||||
var glyph = type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX);
|
||||
var rectangle = glyph.Path.GetBoundingRectangle();
|
||||
var rectangle = PdfSubpath.GetBoundingRectangle(glyph.Path);
|
||||
if (rectangle.HasValue)
|
||||
{
|
||||
return rectangle;
|
||||
@ -77,7 +77,55 @@
|
||||
|
||||
var defaultBoundingBox = TopDictionary.FontBoundingBox;
|
||||
return new PdfRectangle(0, 0, glyph.Width.GetValueOrDefault(), defaultBoundingBox.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pdfpath for the character with the given name.
|
||||
/// </summary>
|
||||
/// <param name="characterName"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryGetPath(string characterName, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
var defaultWidthX = GetDefaultWidthX(characterName);
|
||||
var nominalWidthX = GetNominalWidthX(characterName);
|
||||
|
||||
if (CharStrings.TryGetFirst(out var _))
|
||||
{
|
||||
throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported.");
|
||||
}
|
||||
|
||||
if (!CharStrings.TryGetSecond(out var type2CharStrings))
|
||||
{
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
path = type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX).Path;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetCharacterPath
|
||||
/// </summary>
|
||||
/// <param name="characterName"></param>
|
||||
/// <returns></returns>
|
||||
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||
{
|
||||
var defaultWidthX = GetDefaultWidthX(characterName);
|
||||
var nominalWidthX = GetNominalWidthX(characterName);
|
||||
|
||||
if (CharStrings.TryGetFirst(out var _))
|
||||
{
|
||||
throw new NotImplementedException("Type 1 CharStrings in a CFF font are currently unsupported.");
|
||||
}
|
||||
|
||||
if (!CharStrings.TryGetSecond(out var type2CharStrings))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return type2CharStrings.Generate(characterName, (double)defaultWidthX, (double)nominalWidthX).Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
private readonly IReadOnlyDictionary<string, string> nameToUnicode;
|
||||
private readonly IReadOnlyDictionary<string, string> unicodeToName;
|
||||
|
||||
|
||||
private readonly Dictionary<string, string> oddNameToUnicodeCache = new Dictionary<string, string>();
|
||||
|
||||
private static readonly Lazy<GlyphList> LazyAdobeGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("glyphlist"));
|
||||
@ -138,9 +138,9 @@
|
||||
else if (name.StartsWith("u") && name.Length == 5)
|
||||
{
|
||||
// test for an alternate Unicode name representation uXXXX
|
||||
var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
var codePoint = int.Parse(name.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
|
||||
|
||||
if (codePoint > 0xD7FF && codePoint < 0xE000)
|
||||
if (codePoint > 0xD7FF && codePoint < 0xE000)
|
||||
{
|
||||
throw new InvalidFontFormatException(
|
||||
$"Unicode character name with disallowed code area: {name}");
|
||||
@ -159,4 +159,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using System;
|
||||
using Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal class Glyph : IGlyphDescription
|
||||
{
|
||||
@ -111,12 +112,112 @@
|
||||
|
||||
scaled = matrix.Translate(scaled);
|
||||
|
||||
newPoints[i] = new GlyphPoint((short)scaled.X, (short)scaled.Y, point.IsOnCurve);
|
||||
newPoints[i] = new GlyphPoint((short)scaled.X, (short)scaled.Y, point.IsOnCurve, point.IsEndOfContour);
|
||||
}
|
||||
|
||||
return new Glyph(IsSimple, Instructions, EndPointsOfContours, newPoints, Bounds);
|
||||
}
|
||||
|
||||
#region Subpaths
|
||||
public bool TryGetGlyphPath(out IReadOnlyList<PdfSubpath> subpaths)
|
||||
{
|
||||
subpaths = EmptyArray<PdfSubpath>.Instance;
|
||||
if (Points == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Points.Length > 0)
|
||||
{
|
||||
subpaths = CalculatePath(Points);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PdfSubpath> CalculatePath(GlyphPoint[] points)
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/trunk/fontbox/src/main/java/org/apache/fontbox/ttf/GlyphRenderer.java
|
||||
var path = new List<PdfSubpath>();
|
||||
|
||||
int start = 0;
|
||||
for (int p = 0; p < points.Length; ++p)
|
||||
{
|
||||
if (points[p].IsEndOfContour)
|
||||
{
|
||||
PdfSubpath subpath = new PdfSubpath();
|
||||
GlyphPoint firstPoint = points[start];
|
||||
GlyphPoint lastPoint = points[p];
|
||||
var contour = new List<GlyphPoint>();
|
||||
|
||||
for (int q = start; q <= p; ++q)
|
||||
{
|
||||
contour.Add(points[q]);
|
||||
}
|
||||
|
||||
if (points[start].IsOnCurve)
|
||||
{
|
||||
// using start point at the contour end
|
||||
contour.Add(firstPoint);
|
||||
}
|
||||
else if (points[p].IsOnCurve)
|
||||
{
|
||||
// first is off-curve point, trying to use one from the end
|
||||
contour.Insert(0, lastPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// start and end are off-curve points, creating implicit one
|
||||
var pmid = midValue(firstPoint, lastPoint);
|
||||
contour.Insert(0, pmid);
|
||||
contour.Add(pmid);
|
||||
}
|
||||
|
||||
subpath.MoveTo(contour[0].X, contour[0].Y);
|
||||
for (int j = 1; j < contour.Count; j++)
|
||||
{
|
||||
GlyphPoint pNow = contour[j];
|
||||
if (pNow.IsOnCurve)
|
||||
{
|
||||
subpath.LineTo(pNow.X, pNow.Y);
|
||||
}
|
||||
else if (contour[j + 1].IsOnCurve)
|
||||
{
|
||||
var pPrevious = contour[j - 1];
|
||||
var pNext = contour[j + 1];
|
||||
subpath.BezierCurveTo(pPrevious.X, pPrevious.Y, pNow.X, pNow.Y, pNext.X, pNext.Y);
|
||||
++j;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pPrevious = contour[j - 1];
|
||||
var pmid = midValue(pNow, contour[j + 1]);
|
||||
subpath.BezierCurveTo(pPrevious.X, pPrevious.Y, pNow.X, pNow.Y, pmid.X, pmid.Y);
|
||||
}
|
||||
}
|
||||
subpath.CloseSubpath();
|
||||
path.Add(subpath);
|
||||
start = p + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static short midValue(short a, short b)
|
||||
{
|
||||
return (short)(a + (b - a) / 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This creates an onCurve point that is between point1 and point2.
|
||||
/// </summary>
|
||||
private static GlyphPoint midValue(GlyphPoint point1, GlyphPoint point2)
|
||||
{
|
||||
// this constructs an on-curve, non-endofcountour point
|
||||
return new GlyphPoint(midValue(point1.X, point2.X), midValue(point1.Y, point2.Y), true, false);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var type = IsSimple ? "S" : "C";
|
||||
|
@ -1,5 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using UglyToad.PdfPig.Core;
|
||||
|
||||
internal struct GlyphPoint
|
||||
{
|
||||
public short X { get; }
|
||||
@ -8,16 +10,19 @@
|
||||
|
||||
public bool IsOnCurve { get; }
|
||||
|
||||
public GlyphPoint(short x, short y, bool isOnCurve)
|
||||
public bool IsEndOfContour { get; }
|
||||
|
||||
public GlyphPoint(short x, short y, bool isOnCurve, bool isEndOfContour)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
IsOnCurve = isOnCurve;
|
||||
IsEndOfContour = isEndOfContour;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"({X}, {Y}) | {IsOnCurve}";
|
||||
return $"({X}, {Y}) | {IsOnCurve} | {IsEndOfContour}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
|
||||
{
|
||||
using Core;
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal interface IGlyphDescription : IMergeableGlyph, ITransformableGlyph
|
||||
{
|
||||
@ -16,6 +17,8 @@
|
||||
|
||||
bool IsEmpty { get; }
|
||||
|
||||
bool TryGetGlyphPath(out IReadOnlyList<PdfSubpath> subpaths);
|
||||
|
||||
IGlyphDescription DeepClone();
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
public class TableRegister
|
||||
{
|
||||
/// <summary>
|
||||
/// This table contains global information about the font.
|
||||
/// This table contains global information about the font.
|
||||
/// </summary>
|
||||
public HeaderTable HeaderTable { get; }
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
using Glyphs;
|
||||
using Parser;
|
||||
@ -25,9 +26,9 @@
|
||||
|
||||
private readonly Lazy<IReadOnlyList<IGlyphDescription>> glyphs;
|
||||
public IReadOnlyList<IGlyphDescription> Glyphs => glyphs.Value;
|
||||
|
||||
public GlyphDataTable(TrueTypeHeaderTable directoryTable, IReadOnlyList<uint> glyphOffsets,
|
||||
PdfRectangle maxGlyphBounds,
|
||||
|
||||
public GlyphDataTable(TrueTypeHeaderTable directoryTable, IReadOnlyList<uint> glyphOffsets,
|
||||
PdfRectangle maxGlyphBounds,
|
||||
TrueTypeDataBytes tableBytes)
|
||||
{
|
||||
this.glyphOffsets = glyphOffsets;
|
||||
@ -69,7 +70,7 @@
|
||||
bounds = new PdfRectangle(0, 0, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
tableBytes.Seek(offset);
|
||||
|
||||
// ReSharper disable once UnusedVariable
|
||||
@ -91,7 +92,7 @@
|
||||
|
||||
var bytes = data.ReadByteArray((int)table.Length);
|
||||
|
||||
return new GlyphDataTable(table, tableRegister.IndexToLocationTable.GlyphOffsets,
|
||||
return new GlyphDataTable(table, tableRegister.IndexToLocationTable.GlyphOffsets,
|
||||
tableRegister.HeaderTable.Bounds,
|
||||
new TrueTypeDataBytes(bytes));
|
||||
}
|
||||
@ -164,7 +165,7 @@
|
||||
if (contourCount == 0)
|
||||
{
|
||||
return new Glyph(true, EmptyArray<byte>.Instance, EmptyArray<ushort>.Instance,
|
||||
EmptyArray<GlyphPoint>.Instance,
|
||||
EmptyArray<GlyphPoint>.Instance,
|
||||
new PdfRectangle(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
@ -188,11 +189,25 @@
|
||||
var yCoordinates = ReadCoordinates(data, pointCount, flags, SimpleGlyphFlags.YSingleByte,
|
||||
SimpleGlyphFlags.ThisYIsTheSame);
|
||||
|
||||
int endPtIndex = endPointsOfContours.Length - 1;
|
||||
int endPtOfContourIndex = -1;
|
||||
var points = new GlyphPoint[xCoordinates.Length];
|
||||
|
||||
for (var i = xCoordinates.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (endPtOfContourIndex == -1)
|
||||
{
|
||||
endPtOfContourIndex = endPointsOfContours[endPtIndex];
|
||||
}
|
||||
bool endPt = endPtOfContourIndex == i;
|
||||
if (endPt && endPtIndex > 0)
|
||||
{
|
||||
endPtIndex--;
|
||||
endPtOfContourIndex = -1;
|
||||
}
|
||||
|
||||
var isOnCurve = (flags[i] & SimpleGlyphFlags.OnCurve) == SimpleGlyphFlags.OnCurve;
|
||||
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve);
|
||||
points[i] = new GlyphPoint(xCoordinates[i], yCoordinates[i], isOnCurve, endPt);
|
||||
}
|
||||
|
||||
return new Glyph(true, instructions, endPointsOfContours, points, bounds);
|
||||
@ -209,12 +224,12 @@
|
||||
data.Seek(compositeLocation.Position);
|
||||
|
||||
var components = new List<CompositeComponent>();
|
||||
|
||||
|
||||
// First recursively find all components and ensure they are available.
|
||||
CompositeGlyphFlags flags;
|
||||
do
|
||||
{
|
||||
flags = (CompositeGlyphFlags) data.ReadUnsignedShort();
|
||||
flags = (CompositeGlyphFlags)data.ReadUnsignedShort();
|
||||
var glyphIndex = data.ReadUnsignedShort();
|
||||
|
||||
var childGlyph = glyphs[glyphIndex];
|
||||
@ -232,7 +247,7 @@
|
||||
|
||||
glyphs[glyphIndex] = childGlyph;
|
||||
}
|
||||
|
||||
|
||||
short arg1, arg2;
|
||||
if (HasFlag(flags, CompositeGlyphFlags.Args1And2AreWords))
|
||||
{
|
||||
@ -276,7 +291,6 @@
|
||||
{
|
||||
// TODO: Not implemented, it is unclear how to do this.
|
||||
}
|
||||
|
||||
} while (HasFlag(flags, CompositeGlyphFlags.MoreComponents));
|
||||
|
||||
// Now build the final glyph from the components.
|
||||
@ -381,12 +395,12 @@
|
||||
/// Stores the position after reading the contour count and bounds.
|
||||
/// </summary>
|
||||
public long Position { get; }
|
||||
|
||||
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
|
||||
public TemporaryCompositeLocation(long position, PdfRectangle bounds, short contourCount)
|
||||
{
|
||||
if (contourCount >= 0 )
|
||||
if (contourCount >= 0)
|
||||
{
|
||||
throw new ArgumentException($"A composite glyph should not have a positive contour count. Got: {contourCount}.", nameof(contourCount));
|
||||
}
|
||||
|
@ -112,7 +112,7 @@
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!TableRegister.GlyphTable.TryGetGlyphBounds(index, out boundingBox))
|
||||
{
|
||||
return false;
|
||||
@ -126,6 +126,28 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the bounding box for a glyph representing the specified character code if present.
|
||||
/// </summary>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => TryGetPath(characterCode, null, out path);
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the path for a glyph representing the specified character code if present.
|
||||
/// Uses a custom mapping of character code to glyph index.
|
||||
/// </summary>
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = EmptyArray<PdfSubpath>.Instance;
|
||||
|
||||
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index)
|
||||
|| TableRegister.GlyphTable == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TableRegister.GlyphTable.Glyphs[index].TryGetGlyphPath(out path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get the advance width for a glyph representing the specified character code if present.
|
||||
/// </summary>
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
context.Path.CloseSubpath();
|
||||
context.Path[context.Path.Count - 1].CloseSubpath();
|
||||
context.Stack.Clear();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
var deltaX = context.Stack.PopBottom();
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
|
||||
context.Path.LineTo(x, context.CurrentPosition.Y);
|
||||
context.Path[context.Path.Count - 1].LineTo(x, context.CurrentPosition.Y);
|
||||
context.CurrentPosition = new PdfPoint(x, context.CurrentPosition.Y);
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -31,7 +31,9 @@
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y;
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
|
||||
context.Path.Add(new PdfSubpath());
|
||||
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -34,7 +34,7 @@
|
||||
var x3 = x2;
|
||||
var y3 = y2 + dy3;
|
||||
|
||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -25,7 +25,7 @@
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y + deltaY;
|
||||
|
||||
context.Path.LineTo(x, y);
|
||||
context.Path[context.Path.Count - 1].LineTo(x, y);
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -37,7 +37,9 @@
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y + deltaY;
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
|
||||
context.Path.Add(new PdfSubpath());
|
||||
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -37,7 +37,7 @@
|
||||
var x3 = x2 + dx3;
|
||||
var y3 = y2 + dy3;
|
||||
|
||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
|
||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
var deltaY = context.Stack.PopBottom();
|
||||
var y = context.CurrentPosition.Y + deltaY;
|
||||
|
||||
context.Path.LineTo(context.CurrentPosition.X, y);
|
||||
context.Path[context.Path.Count - 1].LineTo(context.CurrentPosition.X, y);
|
||||
context.CurrentPosition = new PdfPoint(context.CurrentPosition.X, y);
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -33,7 +33,9 @@
|
||||
var x = context.CurrentPosition.X;
|
||||
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
|
||||
context.Path.Add(new PdfSubpath());
|
||||
context.Path[context.Path.Count - 1].MoveTo(x, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -35,7 +35,7 @@
|
||||
var x3 = x2 + dx3;
|
||||
var y3 = y2;
|
||||
|
||||
context.Path.BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
context.Path[context.Path.Count - 1].BezierCurveTo(x1, y1, x2, y2, x3, y3);
|
||||
context.CurrentPosition = new PdfPoint(x3, y3);
|
||||
|
||||
context.Stack.Clear();
|
||||
|
@ -2,12 +2,13 @@
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
|
||||
internal class Type1BuildCharContext
|
||||
{
|
||||
private readonly Func<int, PdfSubpath> characterByIndexFactory;
|
||||
private readonly Func<string, PdfSubpath> characterByNameFactory;
|
||||
private readonly Func<int, IReadOnlyList<PdfSubpath>> characterByIndexFactory;
|
||||
private readonly Func<string, IReadOnlyList<PdfSubpath>> characterByNameFactory;
|
||||
public IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> Subroutines { get; }
|
||||
|
||||
public double WidthX { get; set; }
|
||||
@ -20,7 +21,7 @@
|
||||
|
||||
public bool IsFlexing { get; set; }
|
||||
|
||||
public PdfSubpath Path { get; private set; } = new PdfSubpath();
|
||||
public List<PdfSubpath> Path { get; private set; } = new List<PdfSubpath>();
|
||||
|
||||
public PdfPoint CurrentPosition { get; set; }
|
||||
|
||||
@ -31,8 +32,8 @@
|
||||
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
||||
|
||||
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
||||
Func<int, PdfSubpath> characterByIndexFactory,
|
||||
Func<string, PdfSubpath> characterByNameFactory)
|
||||
Func<int, IReadOnlyList<PdfSubpath>> characterByIndexFactory,
|
||||
Func<string, IReadOnlyList<PdfSubpath>> characterByNameFactory)
|
||||
{
|
||||
this.characterByIndexFactory = characterByIndexFactory ?? throw new ArgumentNullException(nameof(characterByIndexFactory));
|
||||
this.characterByNameFactory = characterByNameFactory ?? throw new ArgumentNullException(nameof(characterByNameFactory));
|
||||
@ -44,19 +45,19 @@
|
||||
FlexPoints.Add(point);
|
||||
}
|
||||
|
||||
public PdfSubpath GetCharacter(int characterCode)
|
||||
public IReadOnlyList<PdfSubpath> GetCharacter(int characterCode)
|
||||
{
|
||||
return characterByIndexFactory(characterCode);
|
||||
}
|
||||
|
||||
public PdfSubpath GetCharacter(string characterName)
|
||||
public IReadOnlyList<PdfSubpath> GetCharacter(string characterName)
|
||||
{
|
||||
return characterByNameFactory(characterName);
|
||||
}
|
||||
|
||||
public void SetPath(PdfSubpath path)
|
||||
public void SetPath(IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
Path = path ?? throw new ArgumentNullException(nameof(path));
|
||||
Path = path.ToList() ?? throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
public void ClearFlexPoints()
|
||||
|
@ -1,35 +1,39 @@
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Commands;
|
||||
using Commands.Arithmetic;
|
||||
using Commands.Hint;
|
||||
using Commands.PathConstruction;
|
||||
using Commands.StartFinishOutline;
|
||||
using Core;
|
||||
using Parser;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a set of CharStrings to their corresponding Type 1 BuildChar operations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A charstring is an encrypted sequence of unsigned 8-bit bytes that encode integers and commands.
|
||||
/// <para>
|
||||
/// A charstring is an encrypted sequence of unsigned 8-bit bytes that encode integers and commands.
|
||||
/// Type 1 BuildChar, when interpreting a charstring, will first decrypt it and then will decode
|
||||
/// its bytes one at a time in sequence.
|
||||
///
|
||||
/// its bytes one at a time in sequence.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The value in a byte indicates a command, a number, or subsequent bytes that are to be interpreted
|
||||
/// in a special way.
|
||||
///
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Once the bytes are decoded into numbers and commands, the execution of these numbers and commands proceeds in a
|
||||
/// manner similar to the operation of the PostScript language. Type 1 BuildChar uses its own operand stack,
|
||||
/// manner similar to the operation of the PostScript language. Type 1 BuildChar uses its own operand stack,
|
||||
/// called the Type 1 BuildChar operand stack, that is distinct from the PostScript interpreter operand stack.
|
||||
///
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This stack holds up to 24 numeric entries. A number, decoded from a charstring, is pushed onto the Type 1
|
||||
/// BuildChar operand stack. A command expects its arguments in order on this operand stack with all arguments generally taken
|
||||
/// from the bottom of the stack (first argument bottom-most);
|
||||
/// from the bottom of the stack (first argument bottom-most);
|
||||
/// however, some commands, particularly the subroutine commands, normally work from the top of the stack. If a command returns
|
||||
/// results, they are pushed onto the Type 1 BuildChar operand stack (last result topmost).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static class Type1CharStringParser
|
||||
{
|
||||
|
@ -10,7 +10,7 @@
|
||||
{
|
||||
private readonly IReadOnlyDictionary<int, string> charStringIndexToName;
|
||||
private readonly object locker = new object();
|
||||
private readonly Dictionary<string, PdfSubpath> glyphs = new Dictionary<string, PdfSubpath>();
|
||||
private readonly Dictionary<string, IReadOnlyList<PdfSubpath>> glyphs = new Dictionary<string, IReadOnlyList<PdfSubpath>>();
|
||||
|
||||
public IReadOnlyDictionary<string, CommandSequence> CharStrings { get; }
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
Subroutines = subroutines ?? throw new ArgumentNullException(nameof(subroutines));
|
||||
}
|
||||
|
||||
public bool TryGenerate(string name, out PdfSubpath path)
|
||||
public bool TryGenerate(string name, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = default(PdfSubpath);
|
||||
path = new List<PdfSubpath>();
|
||||
lock (locker)
|
||||
{
|
||||
if (glyphs.TryGetValue(name, out path))
|
||||
@ -54,7 +54,7 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
private PdfSubpath Run(CommandSequence sequence)
|
||||
private IReadOnlyList<PdfSubpath> Run(CommandSequence sequence)
|
||||
{
|
||||
var context = new Type1BuildCharContext(Subroutines, i =>
|
||||
{
|
||||
|
@ -1,9 +1,9 @@
|
||||
namespace UglyToad.PdfPig.Fonts.Type1
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CharStrings;
|
||||
using Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
@ -62,14 +62,8 @@
|
||||
/// </summary>
|
||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||
{
|
||||
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bbox = glyph.GetBoundingRectangle();
|
||||
|
||||
return bbox;
|
||||
var glyph = GetCharacterPath(characterName);
|
||||
return PdfSubpath.GetBoundingRectangle(glyph);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -96,5 +90,17 @@
|
||||
|
||||
return TransformationMatrix.FromValues(a, b, c, d, e, f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the pdfpath for the character with the given name.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||
{
|
||||
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return glyph;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,18 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Fonts.TrueType;
|
||||
using PdfPig.Fonts.TrueType.Parser;
|
||||
using PdfPig.Fonts.TrueType.Tables;
|
||||
using UglyToad.PdfPig.Fonts.TrueType.Glyphs;
|
||||
using UglyToad.PdfPig.Graphics;
|
||||
using Xunit;
|
||||
|
||||
public class TrueTypeFontParserTests
|
||||
@ -183,7 +188,7 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
var font = TrueTypeFontParser.Parse(input);
|
||||
|
||||
var robotoGlyphs = Encoding.ASCII.GetString(TrueTypeTestHelper.GetFileBytes("Roboto-Regular.GlyphData.txt"));
|
||||
var lines = robotoGlyphs.Split(new[] {"\r\n", "\r", "\n"}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = robotoGlyphs.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
@ -205,9 +210,16 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
{
|
||||
Assert.Equal(width, glyph.Bounds.Width);
|
||||
}
|
||||
|
||||
|
||||
Assert.Equal(height, glyph.Bounds.Height);
|
||||
Assert.Equal(points, glyph.Points.Length);
|
||||
if (points > 0)
|
||||
{
|
||||
Assert.True(glyph.Points[glyph.Points.Length - 1].IsEndOfContour);
|
||||
Assert.True(glyph.TryGetGlyphPath(out var subpaths));
|
||||
|
||||
// TODO - more tests on path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,4 +238,3 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@
|
||||
foreach (var charString in result.CharStrings.CharStrings)
|
||||
{
|
||||
Assert.True(result.CharStrings.TryGenerate(charString.Key, out var path));
|
||||
builder.AppendLine(path.ToFullSvg(0));
|
||||
//builder.AppendLine(path.ToFullSvg(0)); // TODO - to restore
|
||||
}
|
||||
|
||||
builder.Append("</body></html>");
|
||||
|
@ -1,6 +1,6 @@
|
||||
using UglyToad.PdfPig.Tests.Integration;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Tests.Integration;
|
||||
using UglyToad.PdfPig.Writer;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Writer
|
||||
@ -18,7 +18,8 @@ namespace UglyToad.PdfPig.Tests.Writer
|
||||
using (var document = PdfDocument.Open(filePath))
|
||||
{
|
||||
var withoutText = PdfTextRemover.RemoveText(filePath);
|
||||
File.WriteAllBytes(@"C:\temp\_tx.pdf", withoutText);
|
||||
WriteFile($"{nameof(TextRemoverRemovesText)}_{file}", withoutText);
|
||||
|
||||
using (var documentWithoutText = PdfDocument.Open(withoutText))
|
||||
{
|
||||
Assert.Equal(document.NumberOfPages, documentWithoutText.NumberOfPages);
|
||||
@ -27,9 +28,27 @@ namespace UglyToad.PdfPig.Tests.Writer
|
||||
Assert.NotEqual(document.GetPage(i).Text, string.Empty);
|
||||
Assert.Equal(documentWithoutText.GetPage(i).Text, string.Empty);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists("Writer"))
|
||||
{
|
||||
Directory.CreateDirectory("Writer");
|
||||
}
|
||||
|
||||
var output = Path.Combine("Writer", $"{name}");
|
||||
|
||||
File.WriteAllBytes(output, bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2010
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32819.101
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig", "UglyToad.PdfPig\UglyToad.PdfPig.csproj", "{57D0610C-87D3-4E0B-B7C2-EC8B765A8288}"
|
||||
EndProject
|
||||
@ -18,9 +18,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Core", "Ugl
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.DocumentLayoutAnalysis", "UglyToad.PdfPig.DocumentLayoutAnalysis\UglyToad.PdfPig.DocumentLayoutAnalysis.csproj", "{60126BCA-6C52-48A9-A0A6-51796C8B0BE7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UglyToad.PdfPig.Tokens", "UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj", "{D840FF69-4250-4B05-9829-5ABEC43EC82C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokens", "UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj", "{D840FF69-4250-4B05-9829-5ABEC43EC82C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UglyToad.PdfPig.Tokenization", "UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj", "{FD005C50-CD2C-497E-8F7E-6D791091E9B0}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UglyToad.PdfPig.Tokenization", "UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj", "{FD005C50-CD2C-497E-8F7E-6D791091E9B0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
@ -128,22 +127,7 @@
|
||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
public PdfRectangle? GetBoundingRectangle()
|
||||
{
|
||||
if (this.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bboxes = this.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x.Value).ToList();
|
||||
if (bboxes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var minX = bboxes.Min(x => x.Left);
|
||||
var minY = bboxes.Min(x => x.Bottom);
|
||||
var maxX = bboxes.Max(x => x.Right);
|
||||
var maxY = bboxes.Max(x => x.Top);
|
||||
return new PdfRectangle(minX, minY, maxX, maxY);
|
||||
return PdfSubpath.GetBoundingRectangle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,7 @@
|
||||
pdfScanner,
|
||||
filterProvider,
|
||||
resourceStore);
|
||||
// ignored for now, is it possible? check the spec...
|
||||
// ignored for now, is it possible? check the spec...
|
||||
}
|
||||
else if (DirectObjectFinder.TryGet<ArrayToken>(contents, pdfScanner, out var array))
|
||||
{
|
||||
|
@ -2,10 +2,12 @@
|
||||
{
|
||||
using Core;
|
||||
using Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// A CID font contains glyph descriptions accessed by
|
||||
/// A CID font contains glyph descriptions accessed by
|
||||
/// CID (character identifier) as character selectors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
@ -51,5 +53,35 @@
|
||||
PdfVector GetPositionVector(int characterIdentifier);
|
||||
|
||||
PdfVector GetDisplacementVector(int characterIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the glyph path for the given character code.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="path">The glyph path for the given character code.</param>
|
||||
bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the glyph path for the given character code.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="characterCodeToGlyphId"></param>
|
||||
/// <param name="path">The glyph path for the given character code.</param>
|
||||
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||
bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="characterCodeToGlyphId"></param>
|
||||
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||
bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
|
||||
/// <summary>
|
||||
@ -18,6 +19,10 @@
|
||||
|
||||
bool TryGetBoundingAdvancedWidth(int characterIdentifier, out double width);
|
||||
|
||||
bool TryGetPath(int characterName, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
int GetFontMatrixMultiplier();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Fonts.CompactFontFormat;
|
||||
|
||||
@ -62,7 +63,6 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TryGetBoundingBox(int characterIdentifier, Func<int, int?> characterCodeToGlyphId, out PdfRectangle boundingBox)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@ -106,5 +106,31 @@
|
||||
#endif
|
||||
return fontCollection.FirstFont;
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = EmptyArray<PdfSubpath>.Instance;
|
||||
|
||||
var font = GetFont();
|
||||
|
||||
if (font.Encoding == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var characterName = GetCharacterName(characterCode);
|
||||
|
||||
if (font.TryGetPath(characterName, out path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Fonts.TrueType;
|
||||
using Fonts.TrueType.Tables;
|
||||
@ -10,7 +11,7 @@
|
||||
private readonly TrueTypeFont font;
|
||||
|
||||
public FontDetails Details { get; }
|
||||
|
||||
|
||||
public PdfCidTrueTypeFont(TrueTypeFont font)
|
||||
{
|
||||
this.font = font ?? throw new ArgumentNullException(nameof(font));
|
||||
@ -33,5 +34,10 @@
|
||||
=> font.TryGetAdvanceWidth(characterIdentifier, characterCodeToGlyphId, out width);
|
||||
|
||||
public int GetFontMatrixMultiplier() => font.GetUnitsPerEm();
|
||||
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => font.TryGetPath(characterCode, out path);
|
||||
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
=> font.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||
}
|
||||
}
|
@ -131,5 +131,37 @@
|
||||
{
|
||||
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
if (fontProgram == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return fontProgram.TryGetPath(characterCode, out path);
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
if (fontProgram == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||
}
|
||||
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return TryGetPath(characterCode, out path);
|
||||
}
|
||||
|
||||
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.CidFonts
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
using Geometry;
|
||||
using Tokens;
|
||||
@ -114,5 +116,34 @@
|
||||
{
|
||||
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
|
||||
}
|
||||
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path) => TryGetPath(characterCode, cidToGid.GetGlyphIndex, out path);
|
||||
|
||||
public bool TryGetPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
if (fontProgram == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return fontProgram.TryGetPath(characterCode, characterCodeToGlyphId, out path);
|
||||
}
|
||||
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (!TryGetPath(characterCode, out path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
path = FontMatrix.Transform(path).ToList();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetNormalisedPath(int characterCode, Func<int, int?> characterCodeToGlyphId, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +1,177 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.Composite
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CidFonts;
|
||||
using Cmap;
|
||||
using Core;
|
||||
using Geometry;
|
||||
using Tokens;
|
||||
using Util.JetBrains.Annotations;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
/// <summary>
|
||||
/// Defines glyphs using a CIDFont
|
||||
/// </summary>
|
||||
internal class Type0Font : IFont, IVerticalWritingSupported
|
||||
{
|
||||
private readonly CMap ucs2CMap;
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly bool isChineseJapaneseOrKorean;
|
||||
private readonly Dictionary<int, CharacterBoundingBox> boundingBoxCache
|
||||
= new Dictionary<int, CharacterBoundingBox>();
|
||||
|
||||
public NameToken Name => BaseFont;
|
||||
|
||||
[NotNull]
|
||||
public NameToken BaseFont { get; }
|
||||
|
||||
[NotNull]
|
||||
public ICidFont CidFont { get; }
|
||||
|
||||
[NotNull]
|
||||
public CMap CMap { get; }
|
||||
|
||||
[NotNull]
|
||||
public ToUnicodeCMap ToUnicode { get; }
|
||||
|
||||
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
|
||||
|
||||
public FontDetails Details { get; }
|
||||
|
||||
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap,
|
||||
CMap ucs2CMap,
|
||||
bool isChineseJapaneseOrKorean)
|
||||
{
|
||||
this.ucs2CMap = ucs2CMap;
|
||||
this.isChineseJapaneseOrKorean = isChineseJapaneseOrKorean;
|
||||
|
||||
BaseFont = baseFont ?? throw new ArgumentNullException(nameof(baseFont));
|
||||
CidFont = cidFont ?? throw new ArgumentNullException(nameof(cidFont));
|
||||
CMap = cmap ?? throw new ArgumentNullException(nameof(cmap));
|
||||
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
||||
Details = cidFont.Details?.WithName(Name.Data)
|
||||
?? FontDetails.GetDefault(Name.Data);
|
||||
}
|
||||
|
||||
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)
|
||||
{
|
||||
var current = bytes.CurrentOffset;
|
||||
|
||||
var code = CMap.ReadCode(bytes);
|
||||
|
||||
codeLength = (int)(bytes.CurrentOffset - current);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
public bool TryGetUnicode(int characterCode, out string value)
|
||||
{
|
||||
namespace UglyToad.PdfPig.PdfFonts.Composite
|
||||
{
|
||||
using CidFonts;
|
||||
using Cmap;
|
||||
using Core;
|
||||
using Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
using Util.JetBrains.Annotations;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
|
||||
/// <summary>
|
||||
/// Defines glyphs using a CIDFont
|
||||
/// </summary>
|
||||
internal class Type0Font : IFont, IVerticalWritingSupported
|
||||
{
|
||||
private readonly CMap ucs2CMap;
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly bool isChineseJapaneseOrKorean;
|
||||
private readonly Dictionary<int, CharacterBoundingBox> boundingBoxCache
|
||||
= new Dictionary<int, CharacterBoundingBox>();
|
||||
|
||||
public NameToken Name => BaseFont;
|
||||
|
||||
[NotNull]
|
||||
public NameToken BaseFont { get; }
|
||||
|
||||
[NotNull]
|
||||
public ICidFont CidFont { get; }
|
||||
|
||||
[NotNull]
|
||||
public CMap CMap { get; }
|
||||
|
||||
[NotNull]
|
||||
public ToUnicodeCMap ToUnicode { get; }
|
||||
|
||||
public bool IsVertical => CMap.WritingMode == WritingMode.Vertical;
|
||||
|
||||
public FontDetails Details { get; }
|
||||
|
||||
public Type0Font(NameToken baseFont, ICidFont cidFont, CMap cmap, CMap toUnicodeCMap,
|
||||
CMap ucs2CMap,
|
||||
bool isChineseJapaneseOrKorean)
|
||||
{
|
||||
this.ucs2CMap = ucs2CMap;
|
||||
this.isChineseJapaneseOrKorean = isChineseJapaneseOrKorean;
|
||||
|
||||
BaseFont = baseFont ?? throw new ArgumentNullException(nameof(baseFont));
|
||||
CidFont = cidFont ?? throw new ArgumentNullException(nameof(cidFont));
|
||||
CMap = cmap ?? throw new ArgumentNullException(nameof(cmap));
|
||||
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
||||
Details = cidFont.Details?.WithName(Name.Data)
|
||||
?? FontDetails.GetDefault(Name.Data);
|
||||
}
|
||||
|
||||
public int ReadCharacterCode(IInputBytes bytes, out int codeLength)
|
||||
{
|
||||
var current = bytes.CurrentOffset;
|
||||
|
||||
var code = CMap.ReadCode(bytes);
|
||||
|
||||
codeLength = (int)(bytes.CurrentOffset - current);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
public bool TryGetUnicode(int characterCode, out string value)
|
||||
{
|
||||
value = null;
|
||||
|
||||
var HaveCMap = ToUnicode.CanMapToUnicode;
|
||||
if (HaveCMap == false)
|
||||
{
|
||||
var HaveUnicode2CMap = (ucs2CMap is null == false);
|
||||
var HaveCMap = ToUnicode.CanMapToUnicode;
|
||||
if (HaveCMap == false)
|
||||
{
|
||||
var HaveUnicode2CMap = (ucs2CMap is null == false);
|
||||
if (HaveUnicode2CMap)
|
||||
{
|
||||
// Have both ucs2Map and CMap convert to unicode by
|
||||
// characterCode ----by CMAP---> CID ---ucs2Map---> Unicode
|
||||
var CID = CMap.ConvertToCid(characterCode);
|
||||
if (CID == 0)
|
||||
{
|
||||
Debug.WriteLine($"Warning: No mapping from characterCode (0x{characterCode:X} to CID by ucs2Map.");
|
||||
return false; // No mapping from characterCode to CID.
|
||||
}
|
||||
// CID ---ucs2Map---> Unicode
|
||||
if (ucs2CMap.TryConvertToUnicode(CID, out value))
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
|
||||
}
|
||||
if (HaveUnicode2CMap) // 2022-12-24 @fnatzke left as fall-back. Possible?
|
||||
{
|
||||
// characterCode ---ucs2Map---> Unicode (?) @fnatzke possible?
|
||||
if (ucs2CMap.TryConvertToUnicode(characterCode, out value))
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// According to PdfBox certain providers incorrectly using Identity CMaps as ToUnicode.
|
||||
if (ToUnicode.IsUsingIdentityAsUnicodeMap)
|
||||
{
|
||||
value = new string((char)characterCode, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ToUnicode.TryGet(characterCode, out value);
|
||||
}
|
||||
|
||||
public CharacterBoundingBox GetBoundingBox(int characterCode)
|
||||
{
|
||||
if (boundingBoxCache.TryGetValue(characterCode, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var matrix = GetFontMatrix();
|
||||
|
||||
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
||||
|
||||
boundingBox = matrix.Transform(boundingBox);
|
||||
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
var width = CidFont.GetWidthFromFont(characterIdentifier);
|
||||
|
||||
var advanceWidth = matrix.TransformX(width);
|
||||
|
||||
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
|
||||
|
||||
boundingBoxCache[characterCode] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetBoundingBox(characterIdentifier);
|
||||
}
|
||||
|
||||
public TransformationMatrix GetFontMatrix()
|
||||
{
|
||||
return CidFont.FontMatrix;
|
||||
}
|
||||
|
||||
public PdfVector GetPositionVector(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetPositionVector(characterIdentifier).Scale(-1 / 1000.0);
|
||||
}
|
||||
|
||||
public PdfVector GetDisplacementVector(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetDisplacementVector(characterIdentifier).Scale(1 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
var CID = CMap.ConvertToCid(characterCode);
|
||||
if (CID == 0)
|
||||
{
|
||||
Debug.WriteLine($"Warning: No mapping from characterCode (0x{characterCode:X} to CID by ucs2Map.");
|
||||
return false; // No mapping from characterCode to CID.
|
||||
}
|
||||
// CID ---ucs2Map---> Unicode
|
||||
if (ucs2CMap.TryConvertToUnicode(CID, out value))
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
if (HaveUnicode2CMap) // 2022-12-24 @fnatzke left as fall-back. Possible?
|
||||
{
|
||||
// characterCode ---ucs2Map---> Unicode (?) @fnatzke possible?
|
||||
if (ucs2CMap.TryConvertToUnicode(characterCode, out value))
|
||||
{
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// According to PdfBox certain providers incorrectly using Identity CMaps as ToUnicode.
|
||||
if (ToUnicode.IsUsingIdentityAsUnicodeMap)
|
||||
{
|
||||
value = new string((char)characterCode, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return ToUnicode.TryGet(characterCode, out value);
|
||||
}
|
||||
|
||||
public CharacterBoundingBox GetBoundingBox(int characterCode)
|
||||
{
|
||||
if (boundingBoxCache.TryGetValue(characterCode, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
var matrix = GetFontMatrix();
|
||||
|
||||
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
||||
|
||||
boundingBox = matrix.Transform(boundingBox);
|
||||
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
var width = CidFont.GetWidthFromFont(characterIdentifier);
|
||||
|
||||
var advanceWidth = matrix.TransformX(width);
|
||||
|
||||
var result = new CharacterBoundingBox(boundingBox, advanceWidth);
|
||||
|
||||
boundingBoxCache[characterCode] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetBoundingBox(characterIdentifier);
|
||||
}
|
||||
|
||||
public TransformationMatrix GetFontMatrix()
|
||||
{
|
||||
return CidFont.FontMatrix;
|
||||
}
|
||||
|
||||
public PdfVector GetPositionVector(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetPositionVector(characterIdentifier).Scale(-1 / 1000.0);
|
||||
}
|
||||
|
||||
public PdfVector GetDisplacementVector(int characterCode)
|
||||
{
|
||||
var characterIdentifier = CMap.ConvertToCid(characterCode);
|
||||
|
||||
return CidFont.GetDisplacementVector(characterIdentifier).Scale(1 / 1000.0);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return CidFont.TryGetPath(characterCode, out path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return CidFont.TryGetNormalisedPath(characterCode, out path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts
|
||||
{
|
||||
using Core;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
|
||||
internal interface IFont
|
||||
{
|
||||
NameToken Name { get; }
|
||||
|
||||
|
||||
bool IsVertical { get; }
|
||||
|
||||
FontDetails Details { get; }
|
||||
@ -18,5 +19,19 @@
|
||||
CharacterBoundingBox GetBoundingBox(int characterCode);
|
||||
|
||||
TransformationMatrix GetFontMatrix();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the glyph path for the given character code.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="path">The glyph path for the given character code.</param>
|
||||
bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the normalised glyph path for the given character code in a PDF.
|
||||
/// </summary>
|
||||
/// <param name="characterCode">Character code in a PDF. Not to be confused with unicode.</param>
|
||||
/// <param name="path">The normalized glyph path for the given character code.</param>
|
||||
bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path);
|
||||
}
|
||||
}
|
||||
|
@ -114,11 +114,11 @@
|
||||
switch (descriptor.FontFile.FileType)
|
||||
{
|
||||
case DescriptorFontFile.FontFileType.TrueType:
|
||||
{
|
||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile));
|
||||
var ttf = TrueTypeFontParser.Parse(input);
|
||||
return new PdfCidTrueTypeFont(ttf);
|
||||
}
|
||||
{
|
||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(fontFile));
|
||||
var ttf = TrueTypeFontParser.Parse(input);
|
||||
return new PdfCidTrueTypeFont(ttf);
|
||||
}
|
||||
case DescriptorFontFile.FontFileType.FromSubtype:
|
||||
{
|
||||
if (!DirectObjectFinder.TryGet(descriptor.FontFile.ObjectKey, pdfScanner, out StreamToken str))
|
||||
@ -145,7 +145,7 @@
|
||||
var ttf = TrueTypeFontParser.Parse(new TrueTypeDataBytes(new ByteArrayInputBytes(bytes)));
|
||||
return new PdfCidTrueTypeFont(ttf);
|
||||
}
|
||||
|
||||
|
||||
throw new PdfDocumentFormatException($"Unexpected subtype for CID font: {subtypeName}.");
|
||||
}
|
||||
default:
|
||||
|
@ -1,13 +1,14 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cmap;
|
||||
using Composite;
|
||||
using Core;
|
||||
using Fonts;
|
||||
using Fonts.Encodings;
|
||||
using Fonts.TrueType;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Tokens;
|
||||
using Util.JetBrains.Annotations;
|
||||
|
||||
@ -57,7 +58,7 @@
|
||||
Name = name;
|
||||
IsVertical = false;
|
||||
ToUnicode = new ToUnicodeCMap(toUnicodeCMap);
|
||||
Details = descriptor?.ToDetails(Name?.Data)
|
||||
Details = descriptor?.ToDetails(Name?.Data)
|
||||
?? FontDetails.GetDefault(Name?.Data);
|
||||
}
|
||||
|
||||
@ -321,6 +322,29 @@
|
||||
|
||||
return widths[index];
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (font == null)
|
||||
{
|
||||
path = EmptyArray<PdfSubpath>.Instance;
|
||||
return false;
|
||||
}
|
||||
|
||||
return font.TryGetPath(characterCode, CharacterCodeToGlyphId, out path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (!TryGetPath(characterCode, out path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
path = GetFontMatrix().Transform(path).ToList();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Core;
|
||||
using Fonts;
|
||||
using Fonts.AdobeFontMetrics;
|
||||
@ -132,6 +133,29 @@
|
||||
return DefaultTransformation;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
if (font == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return font.TryGetPath(characterCode, out path);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (!TryGetPath(characterCode, out path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
path = GetFontMatrix().Transform(path).ToList();
|
||||
return true;
|
||||
}
|
||||
|
||||
public class MetricOverrides
|
||||
{
|
||||
public int? FirstCharacterCode { get; }
|
||||
|
@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Cmap;
|
||||
using Composite;
|
||||
using Core;
|
||||
@ -43,7 +44,7 @@
|
||||
|
||||
public FontDetails Details { get; }
|
||||
|
||||
public Type1FontSimple(NameToken name, int firstChar, int lastChar, double[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
||||
public Type1FontSimple(NameToken name, int firstChar, int lastChar, double[] widths, FontDescriptor fontDescriptor, Encoding encoding,
|
||||
CMap toUnicodeCMap,
|
||||
Union<Type1Font, CompactFontFormatFontCollection> fontProgram)
|
||||
{
|
||||
@ -72,7 +73,7 @@
|
||||
fontMatrix = matrix;
|
||||
|
||||
Name = name;
|
||||
Details = fontDescriptor?.ToDetails(name?.Data)
|
||||
Details = fontDescriptor?.ToDetails(name?.Data)
|
||||
?? FontDetails.GetDefault(name?.Data);
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@
|
||||
}
|
||||
|
||||
var name = encoding.GetName(characterCode);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
value = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
||||
@ -135,7 +136,7 @@
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
|
||||
var boundingBox = GetBoundingBoxInGlyphSpace(characterCode);
|
||||
|
||||
var matrix = fontMatrix;
|
||||
@ -144,7 +145,7 @@
|
||||
|
||||
var width = GetWidth(characterCode, boundingBox);
|
||||
|
||||
var result = new CharacterBoundingBox(boundingBox, width/1000.0);
|
||||
var result = new CharacterBoundingBox(boundingBox, width / 1000.0);
|
||||
|
||||
cachedBoundingBoxes[characterCode] = result;
|
||||
|
||||
@ -167,7 +168,7 @@
|
||||
|
||||
return boundingBox.Width;
|
||||
}
|
||||
|
||||
|
||||
private PdfRectangle GetBoundingBoxInGlyphSpace(int characterCode)
|
||||
{
|
||||
if (characterCode < firstChar || characterCode > lastChar)
|
||||
@ -183,26 +184,25 @@
|
||||
PdfRectangle? rect = null;
|
||||
if (fontProgram.TryGetFirst(out var t1Font))
|
||||
{
|
||||
var name = encoding.GetName(characterCode);
|
||||
rect = t1Font.GetCharacterBoundingBox(name);
|
||||
var name = encoding.GetName(characterCode);
|
||||
rect = t1Font.GetCharacterBoundingBox(name);
|
||||
}
|
||||
else if (fontProgram.TryGetSecond(out var cffFont))
|
||||
{
|
||||
var first = cffFont.FirstFont;
|
||||
string characterName;
|
||||
if (encoding != null)
|
||||
{
|
||||
characterName = encoding.GetName(characterCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterName = cffFont.GetCharacterName(characterCode);
|
||||
}
|
||||
var first = cffFont.FirstFont;
|
||||
string characterName;
|
||||
if (encoding != null)
|
||||
{
|
||||
characterName = encoding.GetName(characterCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterName = cffFont.GetCharacterName(characterCode);
|
||||
}
|
||||
|
||||
rect = first.GetCharacterBoundingBox(characterName);
|
||||
rect = first.GetCharacterBoundingBox(characterName);
|
||||
}
|
||||
|
||||
|
||||
if (!rect.HasValue)
|
||||
{
|
||||
return new PdfRectangle(0, 0, widths[characterCode - firstChar], 0);
|
||||
@ -216,5 +216,61 @@
|
||||
{
|
||||
return fontMatrix;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
IReadOnlyList<PdfSubpath> tempPath = null;
|
||||
if (characterCode < firstChar || characterCode > lastChar)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fontProgram == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fontProgram.TryGetFirst(out var t1Font))
|
||||
{
|
||||
var name = encoding.GetName(characterCode);
|
||||
tempPath = t1Font.GetCharacterPath(name);
|
||||
}
|
||||
else if (fontProgram.TryGetSecond(out var cffFont))
|
||||
{
|
||||
var first = cffFont.FirstFont;
|
||||
string characterName;
|
||||
if (encoding != null)
|
||||
{
|
||||
characterName = encoding.GetName(characterCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
characterName = cffFont.GetCharacterName(characterCode);
|
||||
}
|
||||
|
||||
tempPath = first.GetCharacterPath(characterName);
|
||||
}
|
||||
|
||||
if (tempPath != null)
|
||||
{
|
||||
path = tempPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
if (TryGetPath(characterCode, out path))
|
||||
{
|
||||
path = fontMatrix.Transform(path).ToList();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
//// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Core;
|
||||
using Fonts;
|
||||
using Fonts.AdobeFontMetrics;
|
||||
using Fonts.Encodings;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
@ -120,5 +121,25 @@ namespace UglyToad.PdfPig.PdfFonts.Simple
|
||||
{
|
||||
return fontMatrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Not implemeted.</para>
|
||||
/// </summary>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/font/Standard14Fonts.java
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Not implemeted.</para>
|
||||
/// </summary>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return TryGetPath(characterCode, out path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
using Core;
|
||||
using Fonts;
|
||||
using Fonts.Encodings;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
|
||||
internal class Type3Font : IFont
|
||||
@ -57,9 +58,7 @@
|
||||
|
||||
var name = encoding.GetName(characterCode);
|
||||
|
||||
var listed = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
||||
|
||||
value = listed;
|
||||
value = GlyphList.AdobeGlyphList.NameToUnicode(name);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -89,5 +88,24 @@
|
||||
{
|
||||
return fontMatrix;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Type 3 fonts do not use vector paths. Always returns <c>false</c>.</para>
|
||||
/// </summary>
|
||||
public bool TryGetPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Type 3 fonts do not use vector paths. Always returns <c>false</c>.</para>
|
||||
/// </summary>
|
||||
public bool TryGetNormalisedPath(int characterCode, out IReadOnlyList<PdfSubpath> path)
|
||||
{
|
||||
return TryGetPath(characterCode, out path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user