Skip to content

Commit 8b82ccc

Browse files
committed
Improve vertical font handling
1 parent 0beb746 commit 8b82ccc

10 files changed

Lines changed: 95 additions & 12 deletions

File tree

Binary file not shown.
5.04 KB
Binary file not shown.
53.7 KB
Loading
56.4 KB
Loading
43.9 KB
Loading

UglyToad.PdfPig.Rendering.Skia.Tests/PageSizeTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ public class PageSizeTests
2323
{
2424
private static readonly HashSet<string> _documentsToIgnore =
2525
[
26-
"Type3Test.pdf" // fails in 0.1.15
26+
"Type3Test.pdf", // fails in 0.1.15
27+
"DefaultColourSpaces.230802.pdf", // fails in 0.1.15
2728
];
2829

2930
public static IEnumerable<object[]> GetAllDocuments => Directory.EnumerateFiles(Helper.DocumentsFolder, "*.pdf")

UglyToad.PdfPig.Rendering.Skia.Tests/TestRendering.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -796,8 +796,13 @@ public class TestRendering
796796
"TextClippingModeChanges_1.png",
797797
"TextClippingModeChanges.pdf", 1, 2
798798
},
799+
new object[]
800+
{
801+
"VerticalText_1.png",
802+
"VerticalText.pdf", 1, 2
803+
},
799804

800-
// TODO - Add Type3Test.pdf test
805+
// TODO - Add Type3Test.pdf + DefaultColourSpaces.230802.pdf test
801806
};
802807

803808
[Theory]

UglyToad.PdfPig.Rendering.Skia.Tests/VisualTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ public class VisualTests
2828
[
2929
"SPARC - v9 Architecture Manual.pdf",
3030
"TIKA-1552-0.pdf",
31-
"Type3Test.pdf" // fails in 0.1.15
31+
"Type3Test.pdf", // fails in 0.1.15
32+
"DefaultColourSpaces.230802.pdf", // fails in 0.1.15
3233
];
3334

3435
public static IEnumerable<object[]> GetAllDocuments => Directory.EnumerateFiles(Helper.DocumentsFolder, "*.pdf")

UglyToad.PdfPig.Rendering.Skia/Helpers/SkiaExtensions.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System;
1616
using System.Runtime.CompilerServices;
1717
using SkiaSharp;
18+
using SkiaSharp.HarfBuzz;
1819
using UglyToad.PdfPig.Core;
1920
using UglyToad.PdfPig.Graphics.Colors;
2021
using UglyToad.PdfPig.Graphics.Core;
@@ -28,6 +29,79 @@ internal static class SkiaExtensions
2829

2930
private static readonly string DefaultFamilyName = SKTypeface.Default.FamilyName;
3031

32+
/// <summary>
33+
/// Draw a shaped glyph run for the fallback (non-vector) text path. Horizontal text uses the
34+
/// default shaper; vertical text is shaped top-to-bottom (see <see cref="ShapeVertical"/>) so
35+
/// HarfBuzz substitutes the vertical presentation forms.
36+
/// </summary>
37+
public static void DrawShapedText(this SKCanvas canvas, SKShaper shaper, string text, SKFont skFont, SKPaint paint, bool vertical)
38+
{
39+
if (!vertical)
40+
{
41+
canvas.DrawShapedText(shaper, text, SKPoint.Empty, SKTextAlign.Left, skFont, paint);
42+
return;
43+
}
44+
45+
// See https://github.com/mono/SkiaSharp/issues/4273
46+
var shaped = shaper.ShapeVertical(text, skFont);
47+
48+
using var builder = new SKTextBlobBuilder();
49+
var run = builder.AllocateRawPositionedRun(skFont, shaped.Codepoints.Length);
50+
var glyphs = run.Glyphs;
51+
var positions = run.Positions;
52+
for (int i = 0; i < shaped.Codepoints.Length; ++i)
53+
{
54+
glyphs[i] = (ushort)shaped.Codepoints[i];
55+
positions[i] = shaped.Points[i];
56+
}
57+
58+
using var blob = builder.Build();
59+
if (blob is not null)
60+
{
61+
canvas.DrawText(blob, 0, 0, paint);
62+
}
63+
else
64+
{
65+
// Fallback to default
66+
canvas.DrawShapedText(shaper, text, SKPoint.Empty, SKTextAlign.Left, skFont, paint);
67+
}
68+
}
69+
70+
/// <summary>
71+
/// Shape <paramref name="text"/> for vertical writing mode, returning the vertical
72+
/// presentation-form glyphs positioned with the horizontal layout.
73+
/// <para>
74+
/// Setting the buffer direction top-to-bottom makes HarfBuzz apply its default 'vert' feature,
75+
/// substituting the vertical presentation forms (rotated brackets, repositioned punctuation, the
76+
/// prolonged-sound mark, ...). We take only the substituted glyph ids from that pass and keep the
77+
/// <em>horizontal</em> glyph positions: PdfPig already places the pen for vertical writing using
78+
/// the font's position vector (W2/DW2), so HarfBuzz's vertical positions — which add the fallback
79+
/// font's own vertical-origin offset on top — would shift every glyph off its bounding box. For
80+
/// the single glyph PdfPig renders per character code the horizontal position is simply the origin.
81+
/// </para>
82+
/// </summary>
83+
public static SKShaper.Result ShapeVertical(this SKShaper shaper, string text, SKFont skFont)
84+
{
85+
// See https://github.com/mono/SkiaSharp/issues/4273
86+
using var buffer = new HarfBuzzSharp.Buffer();
87+
buffer.AddUtf16(text);
88+
buffer.GuessSegmentProperties();
89+
buffer.Direction = HarfBuzzSharp.Direction.TopToBottom;
90+
SKShaper.Result vertical = shaper.Shape(buffer, skFont);
91+
92+
SKShaper.Result horizontal = shaper.Shape(text, skFont);
93+
94+
// 'vert' is a one-to-one (GSUB single) substitution, so the horizontal and vertical passes
95+
// produce the same glyph count in the same order. When that holds, draw the vertical glyphs
96+
// at the horizontal positions; otherwise fall back to the vertical result unchanged.
97+
if (horizontal.Codepoints.Length == vertical.Codepoints.Length)
98+
{
99+
return new SKShaper.Result(vertical.Codepoints, vertical.Clusters, horizontal.Points, horizontal.Width);
100+
}
101+
102+
return vertical;
103+
}
104+
31105
public static bool IsDefault(this SKTypeface typeface)
32106
{
33107
return typeface.FamilyName.Equals(DefaultFamilyName);

UglyToad.PdfPig.Rendering.Skia/SkiaStreamProcessor.Glyph.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
222222
// canvas matrix so cm/q/Q changes later in the same text object don't shift it.
223223
if (textRenderingMode.IsClip())
224224
{
225-
using var glyphPath = GetPath(drawTypeface, renderUnicode);
225+
using var glyphPath = GetPath(drawTypeface, renderUnicode, font.IsVertical);
226226
if (!glyphPath.IsEmpty)
227227
{
228228
AppendGlyphToTextClipPath(glyphPath);
@@ -257,7 +257,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
257257
throw new ArgumentNullException($"Expecting a {nameof(PatternColor)} but got '{nonStrokingColor.GetType()}'.");
258258
}
259259

260-
using (var path = GetPath(drawTypeface, renderUnicode))
260+
using (var path = GetPath(drawTypeface, renderUnicode, font.IsVertical))
261261
{
262262
ShowVectorFontGlyph(path, strokingColor, nonStrokingColor,
263263
textRenderingMode, in TransformationMatrix.Identity, in TransformationMatrix.Identity);
@@ -270,7 +270,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
270270
DrawWithSoftMask(softMask!, currentState.BlendMode, () =>
271271
{
272272
using var skFont = drawTypeface.Typeface.ToFont(1f);
273-
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, SKPoint.Empty, SKTextAlign.Left, skFont, innerPaint);
273+
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, skFont, innerPaint, font.IsVertical);
274274
});
275275
}
276276
else
@@ -280,7 +280,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
280280

281281
using (var skFont = drawTypeface.Typeface.ToFont(1f))
282282
{
283-
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, SKPoint.Empty, SKTextAlign.Left, skFont, fillPaint);
283+
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, skFont, fillPaint, font.IsVertical);
284284
}
285285
}
286286
}
@@ -310,7 +310,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
310310
DrawWithSoftMask(softMask!, currentState.BlendMode, () =>
311311
{
312312
using var skFont = drawTypeface.Typeface.ToFont(1f);
313-
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, SKPoint.Empty, SKTextAlign.Left, skFont, innerStrokePaint);
313+
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, skFont, innerStrokePaint, font.IsVertical);
314314
});
315315
}
316316
else
@@ -321,7 +321,7 @@ private void ShowNonVectorFontGlyph(IFont font, IColor? strokingColor, IColor? n
321321

322322
using (var skFont = drawTypeface.Typeface.ToFont(1f))
323323
{
324-
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, SKPoint.Empty, SKTextAlign.Left, skFont, strokePaint);
324+
_canvas.DrawShapedText(drawTypeface.Shaper, renderUnicode, skFont, strokePaint, font.IsVertical);
325325
}
326326
}
327327
}
@@ -501,11 +501,13 @@ private static SKRect GetType3GlyphBoundingBox(IReadOnlyList<IGraphicsStateOpera
501501
return SKRect.Create(-1000, -1000, 2000, 2000);
502502
}
503503

504-
private static SKPath GetPath(SkiaFontCacheItem fontItem, string unicode)
504+
private static SKPath GetPath(SkiaFontCacheItem fontItem, string unicode, bool vertical)
505505
{
506506
using (var skFont = fontItem.Typeface.ToFont(1f))
507507
{
508-
var shaped = fontItem.Shaper.Shape(unicode, skFont);
508+
var shaped = vertical
509+
? fontItem.Shaper.ShapeVertical(unicode, skFont)
510+
: fontItem.Shaper.Shape(unicode, skFont);
509511

510512
var combinedPath = new SKPath();
511513
for (int i = 0; i < shaped.Codepoints.Length; ++i)
@@ -520,7 +522,7 @@ private static SKPath GetPath(SkiaFontCacheItem fontItem, string unicode)
520522
combinedPath.AddPath(glyphPath, in matrix);
521523
}
522524
}
523-
525+
524526
return combinedPath;
525527
}
526528
}

0 commit comments

Comments
 (0)