parley 0.12.0: `trak` tracking is looked up at the scaled font size
Generated from repros/parley-trak-scaled-point-size/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”Cargo.toml:17-26 patches parley and its sibling crates to oofdere/parley@00cbfc6, which is v0.12.0 plus
linebender/parley#942, so CoreText and niso agree on tracking at device scale 2x/3x (Apple’s system fonts carry
trak tables).
Expected vs actual
Section titled “Expected vs actual”parley 0.12.0 passes ShapeOptions::font_size (CSS size × layout scale) to HarfRust as the point size
(parley_engine/src/shape/shaper.rs:360 at v0.12.0, .point_size(Some(options.font_size))), and HarfRust picks
the trak value for that size. Tracking is defined per point size in CSS px, so text gets wider or narrower when
only the display scale changes.
make_font.py builds a test font from any TTF: renamed to “Trak Test”, OpenType layout tables removed, a trak
table with 0 at 16pt and +300 units at 32pt, and a STAT table (HarfRust only applies trak when STAT is
present, harfrust/src/hb/face.rs:42). 16px text, width divided by the scale:
16px text, width in CSS px: scale 1 = 65.46094, scale 2 = 84.21094expected: equal widths (trak looked up at 16pt at both scales)actual: scale 2 is 18.75 CSS px wider (trak looked up at 32pt)(18.75 = 8 glyphs × 300 units × 16 / 2048 upem.)
python3 -m venv /tmp/ft && /tmp/ft/bin/pip install fonttools/tmp/ft/bin/python make_font.py /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf /tmp/trak.ttfCARGO_TARGET_DIR=~/.capy/work/repro-target cargo run -- /tmp/trak.ttfAny static TTF works as input. Exit status 1 while the bug reproduces.
Upstream main
Section titled “Upstream main”Fixed. With parley from linebender/parley main at 746fdcf0d53fab04c5da1e2d7bde89018cbbb2b4 (and with niso’s
fork at 00cbfc6) the same program prints scale 1 = 65.46094, scale 2 = 65.46094 and exits 0.
- linebender/parley#942 is merged: https://github.com/linebender/parley/pull/942, merged 2026-11-06 as
a724fd1. - No release has it: the newest parley on crates.io is 0.12.0 (2026-10-09); #942 landed after the v0.12.0 tag.
- Blitz main (
be60597f67b3cece0456d26e8d7ff5a88600bbc5, same as niso’s pin) depends onparley = "0.12"and does not build against parley main: patching parley/fontique/etc. to746fdcffails in blitz-dom with three errors:Layout::inline_boxes_mutis gone (layout/inline/layout.rs:510),Layout::alignis gone (layout.rs:992, linebender/parley#941 “Always align when line breaking”), andTextStylehas a new requiredcollapsed_spacefield (stylo_to_parley.rs:517, linebender/parley#887). So niso’s fork stays needed until parley releases 0.13 (or a 0.12.x with #942) and blitz moves to it.
Existing issue or PR
Section titled “Existing issue or PR”The fix is https://github.com/linebender/parley/pull/942 (merged). No separate issue. Nothing to file; at most a request for a 0.12.1 point release with #942, text below.
Issue text
Section titled “Issue text”Title: Release a 0.12.x with #942 (trak looked up at the unscaled size)?
Body:
parley 0.12.0 looks up AAT trak tracking at font_size × scale, so on HiDPI displays text set in Apple system
fonts (which have trak tables) is tracked as if it were 2-3× larger. #942 fixes it on main, but main has breaking
changes (#887, #941, …) that keep downstream crates such as Blitz on 0.12. Would a 0.12.1 with #942 alone be
possible? Repro with a synthesized font (fontTools script adds trak 0 @16pt, +300 @32pt, plus STAT):
use std::sync::Arc;use parley::fontique::{Blob, Collection, CollectionOptions, SourceCache};use parley::{FontContext, FontFamily, Layout, LayoutContext, StyleProperty};
fn width(fcx: &mut FontContext, scale: f32) -> f32 { let mut lcx = LayoutContext::new(); let mut b = lcx.ranged_builder(fcx, "tracking", scale, false); b.push_default(StyleProperty::FontFamily(FontFamily::Source("Trak Test".into()))); b.push_default(StyleProperty::FontSize(16.0)); let mut layout: Layout<()> = b.build("tracking"); layout.break_all_lines(None); layout.width() / scale}
fn main() { let mut fcx = FontContext { collection: Collection::new(CollectionOptions { shared: false, system_fonts: false }), source_cache: SourceCache::default() }; fcx.collection.register_fonts(Blob::new(Arc::new(std::fs::read(std::env::args().nth(1).unwrap()).unwrap())), None); println!("{} vs {}", width(&mut fcx, 1.0), width(&mut fcx, 2.0)); // 0.12.0: 65.46 vs 84.21; main: equal}