CJK: ICF top and bottom from the font's BASE table for grid and character alignment

This commit is contained in:
aiongg
2026-10-10 16:02:52 +00:00
parent d9bc53d7f8
commit b720ff6147
6 changed files with 105 additions and 18 deletions
+5 -8
View File
@@ -16,10 +16,6 @@ use crate::shape::Glyph;
/// Most grid lines one line or paragraph takes (input-derived).
pub(crate) const MAX_GYOUDORI: u32 = 100;
/// The ideographic character face's inset from the em box, top and bottom, as a share of the em.
/// An approximation: fonts with a `BASE` table give `icft` / `icfb`, which aren't read yet.
const ICF_INSET: f64 = 0.05;
#[derive(Clone, Copy, Debug, PartialEq)]
enum Anchor {
First,
@@ -36,21 +32,22 @@ fn anchor(r: Reference) -> Anchor {
}
/// The line's grid reference point below its baseline (negative = above), from its largest
/// character's em box ([`crate::cjk_em_box`], as the leading model measures it).
/// character's em box ([`crate::cjk_em_box`], as the leading model measures it) or ICF box
/// ([`crate::cjk_icf_box`]).
pub(crate) fn reference_offset(line: &[Glyph], r: Reference) -> f64 {
if r == Reference::Baseline {
return 0.0;
}
let Some(g) = line.iter().filter(|g| g.adv > 0.0).max_by(|a, b| a.size.total_cmp(&b.size)) else { return 0.0 };
let (top, bottom) = crate::cjk_em_box(g);
let inset = (bottom - top) * ICF_INSET;
let (icf_top, icf_bottom) = crate::cjk_icf_box(g);
match r {
Reference::Baseline => 0.0,
Reference::EmTop => top,
Reference::EmCenter => (top + bottom) / 2.0,
Reference::EmBottom => bottom,
Reference::IcfTop => top + inset,
Reference::IcfBottom => bottom - inset,
Reference::IcfTop => icf_top,
Reference::IcfBottom => icf_bottom,
}
}
+13 -2
View File
@@ -2087,6 +2087,17 @@ fn cjk_em_box(g: &Glyph) -> (f64, f64) {
let top = -height * g.ascent / (g.ascent + g.descent).max(1e-9);
(top, top + height)
}
/// The ideographic character face (ICF) box relative to the baseline: the em box of
/// [`cjk_em_box`] inset by the font's own ICF insets (`BASE` `icft`/`icfb`, else 5% of the em).
fn cjk_icf_box(g: &Glyph) -> (f64, f64) {
let (top, bottom) = cjk_em_box(g);
let (em_top, em_bottom) = g.face.em_box();
let (icf_top, icf_bottom) = g.face.icf_box();
let em = (em_top - em_bottom).max(1e-9);
let height = bottom - top;
let inset = |v: f64| if v.is_finite() { v.clamp(0.0, 0.5) } else { 0.05 };
(top + inset((em_top - icf_top) / em) * height, bottom - inset((icf_bottom - em_bottom) / em) * height)
}
fn cjk_alignment_shift(g: &Glyph, reference: &Glyph) -> f64 {
use designcraft_doc::cjk::CharacterAlignment as A;
let (t, b) = cjk_em_box(g);
@@ -2096,8 +2107,8 @@ fn cjk_alignment_shift(g: &Glyph, reference: &Glyph) -> f64 {
A::EmTop => t - rt,
A::EmCenter => (t + b - rt - rb) / 2.0,
A::EmBottom => b - rb,
A::IcfTop => reference.ascent - g.ascent,
A::IcfBottom => g.descent - reference.descent,
A::IcfTop => cjk_icf_box(g).0 - cjk_icf_box(reference).0,
A::IcfBottom => cjk_icf_box(g).1 - cjk_icf_box(reference).1,
}
}
fn cjk_line_reference(line: &[Glyph]) -> f64 {