#75 support vertical writing mode fonts

This commit is contained in:
Eliot Jones
2019-10-17 15:57:04 +01:00
parent a2147902a0
commit efe7896824
10 changed files with 277 additions and 29 deletions

View File

@@ -0,0 +1,62 @@
namespace UglyToad.PdfPig.Tests.Fonts.CidFonts
{
using System.Collections.Generic;
using PdfPig.Fonts.CidFonts;
using PdfPig.Geometry;
using Xunit;
public class VerticalWritingMetricsTests
{
private readonly VerticalVectorComponents defaults = new VerticalVectorComponents(250, 600);
[Fact]
public void UsesDefaultWhenOverridesNull()
{
var data = new VerticalWritingMetrics(defaults, null, null);
Assert.Empty(data.IndividualVerticalWritingDisplacements);
Assert.Empty(data.IndividualVerticalWritingPositions);
var position = data.GetPositionVector(60, 250);
Assert.Equal(defaults.Position, position.Y);
var displacement = data.GetDisplacementVector(32);
Assert.Equal(defaults.Displacement, displacement.Y);
}
[Fact]
public void DefaultXComponentsOfVectorsAreCorrect()
{
var data = new VerticalWritingMetrics(defaults, null, null);
var position = data.GetPositionVector(9, 120);
Assert.Equal(120 / 2m, position.X);
var displacement = data.GetDisplacementVector(10);
Assert.Equal(0m, displacement.X);
}
[Fact]
public void UsesVectorOverridesWhenPresent()
{
var data = new VerticalWritingMetrics(defaults, new Dictionary<int, decimal> {{7, 120}},
new Dictionary<int, PdfVector> {{7, new PdfVector(25, 250)}});
var position = data.GetPositionVector(7, 360);
Assert.Equal(25, position.X);
Assert.Equal(250, position.Y);
var displacement = data.GetDisplacementVector(7);
Assert.Equal(0, displacement.X);
Assert.Equal(120, displacement.Y);
var defaultPosition = data.GetPositionVector(6, 100);
Assert.Equal(50, defaultPosition.X);
Assert.Equal(defaults.Position, defaultPosition.Y);
var defaultDisplacement = data.GetDisplacementVector(6);
Assert.Equal(0, defaultDisplacement.X);
Assert.Equal(defaults.Displacement, defaultDisplacement.Y);
}
}
}

View File

@@ -45,5 +45,9 @@
decimal GetWidthFromFont(int characterIdentifier);
PdfRectangle GetBoundingBox(int characterIdentifier);
PdfVector GetPositionVector(int characterIdentifier);
PdfVector GetDisplacementVector(int characterIdentifier);
}
}

View File

@@ -14,20 +14,30 @@
internal class Type0CidFont : ICidFont
{
private readonly ICidFontProgram fontProgram;
private readonly VerticalWritingMetrics verticalWritingMetrics;
public NameToken Type { get; }
public NameToken SubType { get; }
public NameToken BaseFont { get; }
public CharacterIdentifierSystemInfo SystemInfo { get; }
public TransformationMatrix FontMatrix { get; }
public CidFontType CidFontType => CidFontType.Type0;
public FontDescriptor Descriptor { get; }
public IReadOnlyDictionary<int, decimal> Widths { get; }
public Type0CidFont(ICidFontProgram fontProgram, NameToken type, NameToken subType, NameToken baseFont,
CharacterIdentifierSystemInfo systemInfo,
FontDescriptor descriptor, IReadOnlyDictionary<int, decimal> widths)
FontDescriptor descriptor, VerticalWritingMetrics verticalWritingMetrics, IReadOnlyDictionary<int, decimal> widths)
{
this.fontProgram = fontProgram;
this.verticalWritingMetrics = verticalWritingMetrics;
Type = type;
SubType = subType;
BaseFont = baseFont;
@@ -56,9 +66,14 @@
}
// TODO: correct values
if (Descriptor == null)
{
return 250;
}
return Descriptor.MissingWidth;
}
public PdfRectangle GetBoundingBox(int characterIdentifier)
{
// TODO: correct values
@@ -79,5 +94,17 @@
return new PdfRectangle(0, 0, 250, 0);
}
public PdfVector GetPositionVector(int characterIdentifier)
{
var width = GetWidthFromFont(characterIdentifier);
return verticalWritingMetrics.GetPositionVector(characterIdentifier, width);
}
public PdfVector GetDisplacementVector(int characterIdentifier)
{
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
}
}
}

View File

@@ -18,11 +18,17 @@
private readonly CharacterIdentifierToGlyphIndexMap cidToGid;
public NameToken Type { get; }
public NameToken SubType { get; }
public NameToken BaseFont { get; }
public CharacterIdentifierSystemInfo SystemInfo { get; }
public TransformationMatrix FontMatrix { get; }
public CidFontType CidFontType => CidFontType.Type2;
public FontDescriptor Descriptor { get; }
public Type2CidFont(NameToken type, NameToken subType, NameToken baseFont, CharacterIdentifierSystemInfo systemInfo,
@@ -85,5 +91,17 @@
return Descriptor.BoundingBox;
}
public PdfVector GetPositionVector(int characterIdentifier)
{
var width = GetWidthFromFont(characterIdentifier);
return verticalWritingMetrics.GetPositionVector(characterIdentifier, width);
}
public PdfVector GetDisplacementVector(int characterIdentifier)
{
return verticalWritingMetrics.GetDisplacementVector(characterIdentifier);
}
}
}

View File

@@ -1,20 +1,60 @@
namespace UglyToad.PdfPig.Fonts.CidFonts
{
using Geometry;
/// <summary>
/// Equivalent to the DW2 array in the font dictionary for vertical fonts.
/// Defines the default position and displacement vector vertical components
/// for fonts which have vertical writing modes.
/// </summary>
internal struct VerticalVectorComponents
{
/// <summary>
/// The default value of <see cref="VerticalVectorComponents"/> if not defined by a font.
/// </summary>
public static readonly VerticalVectorComponents Default = new VerticalVectorComponents(800, -1000);
/// <summary>
/// The vertical component of the position vector.
/// </summary>
/// <remarks>
/// The full position vector unless overridden by the W2 array is:
/// (w0/2, Position)
/// Where w0 is the width of the given glyph.
/// </remarks>
public decimal Position { get; }
/// <summary>
/// The vertical component of the displacement vector.
/// </summary>
/// <remarks>
/// The full displacement vector is:
/// (0, Displacement)
/// </remarks>
public decimal Displacement { get; }
/// <summary>
/// Create a new <see cref="VerticalVectorComponents"/>.
/// </summary>
public VerticalVectorComponents(decimal position, decimal displacement)
{
Position = position;
Displacement = displacement;
}
public static VerticalVectorComponents Default = new VerticalVectorComponents(800, -1000);
/// <summary>
/// Get the full position vector for a given glyph.
/// </summary>
public PdfVector GetPositionVector(decimal glyphWidth) => new PdfVector(glyphWidth / 2m, Position);
/// <summary>
/// Get the full displacement vector.
/// </summary>
public PdfVector GetDisplacementVector() => new PdfVector(0, Displacement);
/// <inheritdoc />
public override string ToString()
{
return $"Position: {Position}, Displacement: {Displacement}.";
}
}
}

View File

@@ -2,20 +2,70 @@
{
using System.Collections.Generic;
using Geometry;
using Util.JetBrains.Annotations;
/// <summary>
/// Glyphs from fonts which support vertical writing mode define displacement and position vectors.
/// The position vector specifies how the horizontal writing origin is transformed into the vertical writing origin.
/// The displacement vector specifies how far to move vertically before drawing the next glyph.
/// </summary>
internal class VerticalWritingMetrics
{
/// <summary>
/// The default position and displacement vectors where not overridden.
/// </summary>
public VerticalVectorComponents DefaultVerticalWritingMetrics { get; }
/// <summary>
/// Overrides displacement vector y components for glyphs specified by CID code.
/// </summary>
[NotNull]
public IReadOnlyDictionary<int, decimal> IndividualVerticalWritingDisplacements { get; }
/// <summary>
/// Overrides position vector (x and y) components for glyphs specified by CID code.
/// </summary>
[NotNull]
public IReadOnlyDictionary<int, PdfVector> IndividualVerticalWritingPositions { get; }
public VerticalWritingMetrics(VerticalVectorComponents defaultVerticalWritingMetrics, IReadOnlyDictionary<int, decimal> individualVerticalWritingDisplacements, IReadOnlyDictionary<int, PdfVector> individualVerticalWritingPositions)
/// <summary>
/// Create new <see cref="VerticalWritingMetrics"/>.
/// </summary>
public VerticalWritingMetrics(VerticalVectorComponents defaultVerticalWritingMetrics,
[CanBeNull] IReadOnlyDictionary<int, decimal> individualVerticalWritingDisplacements,
[CanBeNull] IReadOnlyDictionary<int, PdfVector> individualVerticalWritingPositions)
{
DefaultVerticalWritingMetrics = defaultVerticalWritingMetrics;
IndividualVerticalWritingDisplacements = individualVerticalWritingDisplacements;
IndividualVerticalWritingPositions = individualVerticalWritingPositions;
IndividualVerticalWritingDisplacements = individualVerticalWritingDisplacements
?? new Dictionary<int, decimal>(0);
IndividualVerticalWritingPositions = individualVerticalWritingPositions
?? new Dictionary<int, PdfVector>(0);
}
/// <summary>
/// Get the position vector used to convert horizontal glyph origin to vertical origin.
/// </summary>
public PdfVector GetPositionVector(int characterIdentifier, decimal glyphWidth)
{
if (IndividualVerticalWritingPositions.TryGetValue(characterIdentifier, out var vector))
{
return vector;
}
return DefaultVerticalWritingMetrics.GetPositionVector(glyphWidth);
}
/// <summary>
/// Get the displacement vector used to move the origin to the next glyph location after drawing.
/// </summary>
public PdfVector GetDisplacementVector(int characterIdentifier)
{
if (IndividualVerticalWritingDisplacements.TryGetValue(characterIdentifier, out var displacementY))
{
return new PdfVector(0, displacementY);
}
return DefaultVerticalWritingMetrics.GetDisplacementVector();
}
}
}

View File

@@ -12,7 +12,7 @@
/// <summary>
/// Defines glyphs using a CIDFont
/// </summary>
internal class Type0Font : IFont
internal class Type0Font : IFont, IVerticalWritingSupported
{
public NameToken Name => BaseFont;
@@ -97,5 +97,19 @@
{
return CidFont.FontMatrix;
}
public PdfVector GetPositionVector(int characterCode)
{
var characterIdentifier = CMap.ConvertToCid(characterCode);
return CidFont.GetPositionVector(characterIdentifier).Scale(-1 / 1000m);
}
public PdfVector GetDisplacementVector(int characterCode)
{
var characterIdentifier = CMap.ConvertToCid(characterCode);
return CidFont.GetDisplacementVector(characterIdentifier).Scale(1 / 1000m);
}
}
}

View File

@@ -0,0 +1,18 @@
namespace UglyToad.PdfPig.Fonts
{
using Geometry;
/// <summary>
/// A font which supports a vertical writing mode in addition to the default horizontal writing mode.
/// </summary>
internal interface IVerticalWritingSupported
{
/// <summary>
/// In vertical fonts the glyph position is described by a position vector from the origin used for horizontal writing.
/// The position vector is applied to the horizontal writing origin to give a new vertical writing origin.
/// </summary>
PdfVector GetPositionVector(int characterCode);
PdfVector GetDisplacementVector(int characterCode);
}
}

View File

@@ -62,7 +62,7 @@
var subType = dictionary.GetNameOrDefault(NameToken.Subtype);
if (NameToken.CidFontType0.Equals(subType))
{
return new Type0CidFont(fontProgram, type, subType, baseFont, systemInfo, descriptor, widths);
return new Type0CidFont(fontProgram, type, subType, baseFont, systemInfo, descriptor, verticalWritingMetrics, widths);
}
if (NameToken.CidFontType2.Equals(subType))
@@ -160,18 +160,18 @@
return widths;
}
int size = widthArray.Data.Count;
int counter = 0;
var size = widthArray.Data.Count;
var counter = 0;
while (counter < size)
{
var firstCode = (NumericToken)widthArray.Data[counter++];
var next = widthArray.Data[counter++];
if (DirectObjectFinder.TryGet(next, pdfScanner, out ArrayToken array))
{
int startRange = firstCode.Int;
int arraySize = array.Data.Count;
var startRange = firstCode.Int;
var arraySize = array.Data.Count;
for (int i = 0; i < arraySize; i++)
for (var i = 0; i < arraySize; i++)
{
var width = (NumericToken)array.Data[i];
widths[startRange + i] = width.Data;
@@ -181,8 +181,8 @@
{
var secondCode = (NumericToken)next;
var rangeWidth = (NumericToken)widthArray.Data[counter++];
int startRange = firstCode.Int;
int endRange = secondCode.Int;
var startRange = firstCode.Int;
var endRange = secondCode.Int;
var width = rangeWidth.Data;
for (var i = startRange; i <= endRange; i++)
{
@@ -199,10 +199,11 @@
var verticalDisplacements = new Dictionary<int, decimal>();
var positionVectors = new Dictionary<int, PdfVector>();
// The default position vector and displacement vector are specified by the DW2 entry.
VerticalVectorComponents dw2;
if (!dict.TryGet(NameToken.Dw2, out var dw2Token) || !(dw2Token is ArrayToken arrayVerticalComponents))
{
dw2 = new VerticalVectorComponents(880, -1000);
dw2 = VerticalVectorComponents.Default;
}
else
{
@@ -222,9 +223,10 @@
if (next is ArrayToken array)
{
for (int j = 0; j < array.Data.Count; j++)
for (var j = 0; j < array.Data.Count; j++)
{
int cid = c.Int + j;
var cid = c.Int + j;
// ReSharper disable InconsistentNaming
var w1y = (NumericToken)array.Data[j];
var v1x = (NumericToken)array.Data[++j];
var v1y = (NumericToken)array.Data[++j];
@@ -236,11 +238,12 @@
}
else
{
int first = c.Int;
int last = ((NumericToken)next).Int;
var first = c.Int;
var last = ((NumericToken)next).Int;
var w1y = (NumericToken)w2.Data[++i];
var v1x = (NumericToken)w2.Data[++i];
var v1y = (NumericToken)w2.Data[++i];
// ReSharper restore InconsistentNaming
for (var cid = first; cid <= last; cid++)
{

View File

@@ -181,21 +181,31 @@
wordSpacing += GetCurrentState().FontState.WordSpacing;
}
var textMatrix = TextMatrices.TextMatrix;
if (font.IsVertical)
{
throw new NotImplementedException("Vertical fonts are currently unsupported, please submit a pull request or issue with an example file.");
if (!(font is IVerticalWritingSupported verticalFont))
{
throw new InvalidOperationException($"Font {font.Name} was in vertical writing mode but did not implement {nameof(IVerticalWritingSupported)}.");
}
var positionVector = verticalFont.GetPositionVector(code);
textMatrix = textMatrix.Translate(positionVector.X, positionVector.Y);
}
var boundingBox = font.GetBoundingBox(code);
var transformedGlyphBounds = rotation.Rotate(transformationMatrix)
.Transform(TextMatrices.TextMatrix
.Transform(textMatrix
.Transform(renderingMatrix
.Transform(boundingBox.GlyphBounds)));
var transformedPdfBounds = rotation.Rotate(transformationMatrix)
.Transform(TextMatrices.TextMatrix
.Transform(renderingMatrix.Transform(new PdfRectangle(0, 0, boundingBox.Width, 0))));
.Transform(textMatrix
.Transform(renderingMatrix
.Transform(new PdfRectangle(0, 0, boundingBox.Width, 0))));
// If the text rendering mode calls for filling, the current nonstroking color in the graphics state is used;
// if it calls for stroking, the current stroking color is used.
@@ -218,8 +228,10 @@
decimal tx, ty;
if (font.IsVertical)
{
var verticalFont = (IVerticalWritingSupported) font;
var displacement = verticalFont.GetDisplacementVector(code);
tx = 0;
ty = boundingBox.GlyphBounds.Height * fontSize + characterSpacing + wordSpacing;
ty = (displacement.Y * fontSize) + characterSpacing + wordSpacing;
}
else
{