mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Type: required lam-alef is not an alternate in fonts that put it in liga (#394)
Highlight Alternate Glyphs compares each glyph with required shaping alone, which turns liga and calt off. Older Arabic fonts (such as the legacy Arial of the Microsoft core fonts) put the mandatory lam-alef ligature in liga instead of rlig, so every lam-alef was highlighted. A lam + alef cluster (any alef form, marks allowed) is now never an alternate candidate, whatever feature the font uses. Adds a test font built in code (lam-alef under liga or rlig) and host-independent regressions. Also two equivalent one-line repairs strict Clippy needs on main: a nonminimal_bool in the Path Precision Tools inspect check and is_none() in a gradient panel test.
This commit is contained in:
@@ -118,7 +118,9 @@ fn arabic_joining_forms_are_not_alternates() {
|
||||
assert_eq!(alternates(&t), 0);
|
||||
}
|
||||
|
||||
/// #394: the lam-alef ligature is required (`rlig`), so it isn't highlighted either.
|
||||
/// #394: the lam-alef ligature is required (`rlig`), so it isn't highlighted either. With an
|
||||
/// older font that files it under `liga` (as the legacy Arial of Microsoft's core fonts does) it
|
||||
/// is still required: [`lam_alef_in_liga_is_not_an_alternate`] checks that without an OS font.
|
||||
#[test]
|
||||
fn lam_alef_is_not_an_alternate() {
|
||||
// Looking up an installed font loads it, which changes the fonts.
|
||||
@@ -129,6 +131,38 @@ fn lam_alef_is_not_an_alternate() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #394: a font whose lam-alef ligature is in `liga` (older Arabic fonts) or `rlig` gets no
|
||||
/// highlight on it: the ligature is required by the characters, not chosen by an optional
|
||||
/// feature. Built in code, so it runs whatever fonts the machine has.
|
||||
#[test]
|
||||
fn lam_alef_in_liga_is_not_an_alternate() {
|
||||
use vectorcraft_text::test_fonts::{lam_alef_family, lam_alef_font};
|
||||
let _adding = crate::tests_fontchange::adding_fonts();
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
for feature in [b"liga", b"rlig"] {
|
||||
db.add_font(lam_alef_font(feature).unwrap());
|
||||
let style = vectorcraft_doc::text::CharStyle { size: 36.0, font_family: lam_alef_family(feature).into(), ..Default::default() };
|
||||
for s in ["\u{0644}\u{0627}", "\u{0644}\u{0623}", "\u{0628}\u{0644}\u{0627}", "\u{0644}\u{064E}\u{0627}"] {
|
||||
let t = text(s, style.clone());
|
||||
// The font's ligature glyph stands for the pair.
|
||||
assert!(glyphs(&t).iter().any(|g| g.1 > 2), "{}: {s} is shaped to one ligature glyph", lam_alef_family(feature));
|
||||
assert_eq!(alternates(&t), 0, "{}: {s}", lam_alef_family(feature));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Only a lam followed by an alef (marks between or after allowed) is the required lam-alef.
|
||||
#[test]
|
||||
fn only_lam_alef_clusters_are_the_required_ligature() {
|
||||
for s in ["\u{0644}\u{0627}", "\u{0644}\u{0622}", "\u{0644}\u{0623}", "\u{0644}\u{0625}", "\u{0644}\u{064E}\u{0627}", "\u{0644}\u{0627}\u{064B}"]
|
||||
{
|
||||
assert!(crate::text_geom::is_lam_alef(s), "{s:?}");
|
||||
}
|
||||
for s in ["", "\u{0644}", "\u{0627}\u{0644}", "\u{0644}\u{0628}", "\u{0628}\u{0644}\u{0627}", "\u{0644}\u{0627}\u{0627}", "fi", "\u{064E}"] {
|
||||
assert!(!crate::text_geom::is_lam_alef(s), "{s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #394: an optional feature the characters turn on (a stylistic set, swash, stylistic or
|
||||
/// discretionary alternate…) is highlighted on the glyphs it changes, and only on those.
|
||||
#[test]
|
||||
|
||||
@@ -48,7 +48,8 @@ impl TextGeom {
|
||||
/// The boxes of the glyphs of `t` (the text this geometry is of) that optional OpenType features
|
||||
/// chose (Preferences › Highlight Alternate Glyphs): candidates the text's required shaping
|
||||
/// alone doesn't give ([`required_shaping`]). Arabic letters joining, Indic conjuncts and the
|
||||
/// lam-alef ligature are what the script needs, not alternates. Measured once.
|
||||
/// lam-alef ligature are what the script needs, not alternates (the lam-alef ligature also when
|
||||
/// a font files it under `liga`: [`is_lam_alef`]). Measured once.
|
||||
pub(crate) fn alternate_glyphs(&self, t: &TextObject) -> &BezPath {
|
||||
self.alternate_glyphs.get_or_init(|| {
|
||||
let mut out = BezPath::new();
|
||||
@@ -84,6 +85,17 @@ pub(crate) fn is_opentype_alternate(nominal_gid: u32, shaped_gid: u32, cluster_c
|
||||
shaped_gid != 0 && (cluster_chars > 1 || shaped_gid != nominal_gid)
|
||||
}
|
||||
|
||||
/// Whether `cluster` is a lam followed by an alef (plain, with madda, or with hamza above or below),
|
||||
/// with any Arabic marks between or after them: the lam-alef ligature, which the Unicode
|
||||
/// Standard's Arabic chapter treats as obligatory, not a stylistic choice. Fonts made before
|
||||
/// `rlig` was common put it in `liga`, which [`required_shaping`] turns off, so it is required
|
||||
/// by its characters whatever feature the font files it under.
|
||||
pub(crate) fn is_lam_alef(cluster: &str) -> bool {
|
||||
let is_mark = |c: &char| matches!(c, '\u{0610}'..='\u{061A}' | '\u{064B}'..='\u{065F}' | '\u{0670}' | '\u{06D6}'..='\u{06DC}' | '\u{06DF}'..='\u{06E4}' | '\u{06E7}' | '\u{06E8}' | '\u{06EA}'..='\u{06ED}');
|
||||
let mut letters = cluster.chars().filter(|c| !is_mark(c));
|
||||
matches!((letters.next(), letters.next(), letters.next()), (Some('\u{0644}'), Some('\u{0622}' | '\u{0623}' | '\u{0625}' | '\u{0627}'), None))
|
||||
}
|
||||
|
||||
/// A glyph [`is_opentype_alternate`] marks, in its cluster of the text (`byte`, `len`), with its
|
||||
/// box.
|
||||
#[derive(Debug)]
|
||||
@@ -181,7 +193,7 @@ pub(crate) fn text_geom_snapped(t: &TextObject, snap: Option<Affine>) -> TextGeo
|
||||
let cluster_chars = cluster.chars().count();
|
||||
let Some(first) = cluster.chars().next() else { continue };
|
||||
let nominal = db.face_by_id(g.font_id).map(|f| f.glyph_for(first)).unwrap_or(0);
|
||||
if is_opentype_alternate(nominal, g.gid, cluster_chars) {
|
||||
if is_opentype_alternate(nominal, g.gid, cluster_chars) && !is_lam_alef(cluster) {
|
||||
alternate_candidates.push(AlternateCandidate { byte: g.byte, len: g.len, font_id: g.font_id, gid: g.gid, cell });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user