Skip to content

Commit 36c92c4

Browse files
taj-pStewartCanva
andauthored
Harfrust for Text Shaping (#400)
### Migrate text shaping from Swash to Harfrust This PR migrates Parley’s shaping engine from Swash to Harfrust, while continuing to use Swash for text analysis and some example rendering. The goal is to align Parley’s shaping behavior with HarfBuzz parity via Harfrust to improve correctness across complex scripts (for example, see the improved and updated RTL Arabic snapshots). The changes are largely internal and should have no (besides `.synthesis()`, I believe) external API changes. ### What changed - **Shaping pipeline** - Replaced Swash shaping with Harfrust - Continued using Swash for text analysis (segmentation, clustering, word boundaries). - **Layout and run storage** - Internal run data stores Harfrust results and variation coordinates. - Parley handling of variation coordinates for variable fonts. ### Follow-ups - We're going to next look at using ICU4X for text analysis. cc @conor-93 --------- Co-authored-by: Stewart Connor <[email protected]>
1 parent 8d5a32c commit 36c92c4

40 files changed

+1163
-320
lines changed

.typos.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Maka = "Maka"
2323
ot = "ot"
2424
ot_a = "ot_a"
2525
ot_b = "ot_b"
26+
VAI = "VAI"
2627
wdth = "wdth"
2728

2829
# Match Inside a Word - Case Insensitive

Cargo.lock

Lines changed: 29 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ repository = "https://github.com/linebender/parley"
2323
accesskit = "0.21.0"
2424
bytemuck = { version = "1.23.0", default-features = false }
2525
fontique = { version = "0.5.0", default-features = false, path = "fontique" }
26+
harfrust = { version = "0.1.2", default-features = false }
2627
hashbrown = "0.15.3"
2728
parley = { version = "0.5.0", default-features = false, path = "parley" }
2829
peniko = { version = "0.4.0", default-features = false }

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ It is backed by [Swash](https://github.com/dfrg/swash).
1717

1818
## The Parley text stack
1919

20-
Currently, Parley directly depends on four crates: Fontique, Swash, Skrifa, and Peniko.
20+
Currently, Parley directly depends on five crates: Fontique, HarfRust, Swash, Skrifa, and Peniko.
2121
These crates cover different pieces of the text-rendering process.
2222

2323
### Peniko
@@ -39,6 +39,11 @@ This is necessary because fonts typically don't cover the entire Unicode range:
3939
But if you have, say arabic text or emoji embedded within latin text, you don't typically specify the font for the arabic text or the emoji, one is chosen for you.
4040
Font fallback is the process which makes that choice.
4141

42+
### HarfRust
43+
44+
HarfRust is a Rust port of HarfBuzz text shaping engine. **Text shaping** means mapping runs of Unicode codepoints to specific glyphs within fonts.
45+
This includes applying ligatures, resolving emoji modifiers, but also much more complex transformations for some scripts.
46+
4247
### Skrifa
4348

4449
Skrifa reads TrueType and OpenType fonts.
@@ -50,15 +55,7 @@ Notably it converts the raw glyph representations in font files into scaled, hin
5055

5156
### Swash
5257

53-
Swash implements text shaping and [some miscellaneous Unicode-related features](https://github.com/dfrg/swash#text-analysis).
54-
55-
**Text shaping** means mapping runs of Unicode codepoints to specific glyphs within fonts.
56-
This includes applying ligatures, resolving emoji modifiers, but also much more complex transformations for some scripts.
57-
58-
Swash's implementation is faster but less complete and tested than Harfbuzz and Rustybuzz.
59-
60-
Swash also implements font parsing, scaling, and hinting.
61-
This part of Swash is now superseded by Skrifa: the implementation in Skrifa is directly descended from the one in Swash.
58+
Within the context of Parley, Swash implements [some miscellaneous Unicode-related features](https://github.com/dfrg/swash#text-analysis).
6259

6360
### Parley
6461

@@ -106,6 +103,7 @@ at your option.
106103

107104
Some files used for tests are under different licenses:
108105

106+
- The font file `Arimo-VariableFont_wght.ttf` in `/parley/tests/assets/arimo_fonts/` is licensed solely as documented in that folder (and is licensed under the Apache License, Version 2.0).
109107
- The font file `Roboto-Regular.ttf` in `/parley/tests/assets/roboto_fonts/` is licensed solely as documented in that folder (and is licensed under the Apache License, Version 2.0).
110108
- The font file `NotoKufiArabic-Regular.otf` in `/parley/tests/assets/noto_fonts/` is licensed solely as documented in that folder (and is licensed under the SIL Open Font License, Version 1.1).
111109

examples/swash_render/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ fn render_glyph(
322322
// Apply the fractional offset
323323
.offset(offset)
324324
// Render the image
325-
.render(scaler, glyph.id)
325+
.render(scaler, glyph.id as u16)
326326
.unwrap();
327327

328328
let glyph_width = rendered_glyph.placement.width;

examples/vello_editor/src/text.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ impl Editor {
408408
let gy = y - glyph.y;
409409
x += glyph.advance;
410410
vello::Glyph {
411-
id: glyph.id as _,
411+
id: glyph.id,
412412
x: gx,
413413
y: gy,
414414
}

fontique/src/font.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ pub struct AxisInfo {
319319
/// as well as [`QueryFont::synthesis`].
320320
///
321321
/// [`QueryFont::synthesis`]: crate::QueryFont::synthesis
322-
#[derive(Copy, Clone, Default)]
322+
#[derive(Copy, Clone, Default, PartialEq)]
323323
pub struct Synthesis {
324324
vars: [(Tag, f32); 3],
325325
len: u8,

parley/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ workspace = true
1818

1919
[features]
2020
default = ["system"]
21-
std = ["fontique/std", "peniko/std", "skrifa/std", "swash/std"]
21+
std = ["fontique/std", "harfrust/std", "peniko/std", "skrifa/std", "swash/std"]
2222
libm = ["fontique/libm", "peniko/libm", "skrifa/libm", "swash/libm", "dep:core_maths"]
2323
# Enables support for system font backends
2424
system = ["std", "fontique/system"]
@@ -32,6 +32,7 @@ fontique = { workspace = true }
3232
core_maths = { version = "0.1.1", optional = true }
3333
accesskit = { workspace = true, optional = true }
3434
hashbrown = { workspace = true }
35+
harfrust = { workspace = true }
3536

3637
[dev-dependencies]
3738
tiny-skia = "0.11.4"

parley/src/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use super::builder::RangedBuilder;
1313
use super::resolve::{RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle, tree};
1414
use super::style::{Brush, TextStyle};
1515

16-
use swash::shape::ShapeContext;
1716
use swash::text::cluster::CharInfo;
1817

1918
use crate::builder::TreeBuilder;
2019
use crate::inline_box::InlineBox;
20+
use crate::shape::ShapeContext;
2121

2222
/// Shared scratch space used when constructing text layouts.
2323
///

parley/src/layout/cluster.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
// Copyright 2021 the Parley Authors
22
// SPDX-License-Identifier: Apache-2.0 OR MIT
33

4-
use super::{
5-
BreakReason, Brush, Cluster, ClusterInfo, Glyph, Layout, Line, LineItem, Range, Run, Style,
6-
};
4+
use super::{BreakReason, Brush, Cluster, Glyph, Layout, Line, LineItem, Range, Run, Style, data};
75
use swash::text::cluster::Whitespace;
86

97
/// Defines the visual side of the cluster for hit testing.
@@ -390,8 +388,8 @@ impl<'a, B: Brush> Cluster<'a, B> {
390388
Some(offset)
391389
}
392390

393-
pub(crate) fn info(&self) -> ClusterInfo {
394-
self.data.info
391+
pub(crate) fn info(&self) -> &data::ClusterInfo {
392+
&self.data.info
395393
}
396394
}
397395

0 commit comments

Comments
 (0)