Text: condensed character spacing keeps pairs kerned

Follow-up to the kerning rule: on the real corpus (ms6: titles condensed by 1 pt from the
master's title style) PowerPoint kerns condensed text, and a closer probe agrees for -0.6, -1,
-1.5, -2 and -3 pt (only -1.2 and -2.5 pt came out unkerned, which no simple rule explains). Only
spacing out by 0.6 pt or more now switches kerning off for the box. ms6 slides 2/7: 4.97 -> 6.44
-> back to 4.97, 7.05 -> 8.10 -> 7.05.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas 2026-10-13 05:16:19 -04:00
parent 08bc46c0ea
commit fa3db9a777
2 changed files with 8 additions and 6 deletions

View File

@ -1332,14 +1332,16 @@ pub fn layout(ctx: &Ctx, shape: &Shape, body: &TextBody, opts: &Opts) -> TextLay
}
/// Does character spacing switch pair kerning off in this body? PowerPoint stops kerning every run
/// of a text body once one of its runs is spaced out by 0.6 pt or more, or condensed by 1 pt or
/// more (measured on Arial at 40 pt: 0.5 pt and -0.99 pt keep the pairs kerned, 0.6 pt and -1 pt
/// drop them for the whole box).
/// of a text body once one of its runs is spaced out by 0.6 pt or more (measured on Arial at 40 pt:
/// 0.5 pt keeps the pairs kerned, 0.6, 0.8, 1 and 1.5 pt drop them for the whole box, even in
/// paragraphs without spacing). Condensed spacing mostly keeps them (-0.6, -1, -1.5, -2 and -3 pt;
/// -1.2 and -2.5 pt did not, which no simple rule explains, so condensing never stops kerning
/// here: titles condensed by 1 pt are common and kerned).
fn spacing_stops_kerning(ctx: &Ctx, shape: &Shape, body: &TextBody) -> bool {
body.paragraphs.iter().any(|para| {
para.runs.iter().any(|run| {
let s = resolve::run(ctx, shape, para, &run.props).spacing.unwrap_or(0.0);
s >= 0.55 || s <= -0.995
s >= 0.55
})
})
}

View File

@ -133,8 +133,8 @@ fn character_spacing_in_the_body_stops_kerning() {
};
let (_, off) = glyphs_and_width(&family, "AVATAR WAVE", Some(0.0));
let (_, on) = glyphs_and_width(&family, "AVATAR WAVE", Some(12.0));
// Small spacing elsewhere keeps the pairs; 0.6 pt or -1 pt anywhere in the box drops them.
for (spc, kerned) in [(0.0, true), (0.5, true), (-0.99, true), (0.6, false), (1.0, false), (-1.0, false), (3.0, false)] {
// Small or condensed spacing elsewhere keeps the pairs; 0.6 pt anywhere in the box drops them.
for (spc, kerned) in [(0.0, true), (0.5, true), (-0.99, true), (-1.0, true), (-2.0, true), (0.6, false), (1.0, false), (3.0, false)] {
let w = kerned_width_beside_spacing(&family, spc);
let want = if kerned { on } else { off };
assert!((w - want).abs() < 0.01, "{family} spc {spc}: {w} vs {want}");