Skip to content

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.

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).

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.21094
expected: 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.)

Terminal window
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.ttf
CARGO_TARGET_DIR=~/.capy/work/repro-target cargo run -- /tmp/trak.ttf

Any static TTF works as input. Exit status 1 while the bug reproduces.

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 on parley = "0.12" and does not build against parley main: patching parley/fontique/etc. to 746fdcf fails in blitz-dom with three errors: Layout::inline_boxes_mut is gone (layout/inline/layout.rs:510), Layout::align is gone (layout.rs:992, linebender/parley#941 “Always align when line breaking”), and TextStyle has a new required collapsed_space field (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.

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.

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
}