mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-11 16:31:54 -06:00
Layout: a no-break space is as wide as a space, not as the font's own U+00A0 glyph (#4210)
* Layout: a no-break space is as wide as a space, not as the font's own U+00A0 glyph (fixes #4192) * Layout parity: a no-break space is as wide as a space (#4192)
This commit is contained in:
@@ -4939,6 +4939,8 @@ mod tests_grid;
|
||||
#[cfg(test)]
|
||||
mod tests_ligature;
|
||||
#[cfg(test)]
|
||||
mod tests_nbsp;
|
||||
#[cfg(test)]
|
||||
mod tests_numeral;
|
||||
#[cfg(test)]
|
||||
mod tests_pages;
|
||||
|
||||
@@ -1066,6 +1066,10 @@ impl<'a> Builder<'a> {
|
||||
let d = show(c);
|
||||
if d != c {
|
||||
d
|
||||
} else if c == '\u{a0}' {
|
||||
// Word sets a no-break space as wide as a space; some fonts draw their own
|
||||
// U+00A0 glyph wider. Only the shaping changes: line breaking reads the text.
|
||||
' '
|
||||
} else if upper {
|
||||
upper_char(c)
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
//! A no-break space is laid out as wide as an ordinary space, whatever the font's own U+00A0
|
||||
//! glyph measures (Lucida Bright draws it twice as wide).
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Inter with its U+00A0 glyph twice as wide as its space, under the family name "Wnbsp".
|
||||
/// Built from the bundled font so the test needs no installed font.
|
||||
fn wide_nbsp_family() -> &'static str {
|
||||
const FAMILY: &str = "Wnbsp";
|
||||
let inter = wordcraft_fonts::word::resolve("Inter", false, false).face;
|
||||
let mut data = inter.data().to_vec();
|
||||
let gid = inter.glyph_for('\u{a0}') as usize;
|
||||
assert_ne!(gid, inter.glyph_for(' ') as usize, "Inter keeps a glyph of its own for U+00A0");
|
||||
let u16_at = |d: &[u8], o: usize| u16::from_be_bytes([d[o], d[o + 1]]) as usize;
|
||||
let u32_at = |d: &[u8], o: usize| u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) as usize;
|
||||
let table = |d: &[u8], tag: &[u8; 4]| {
|
||||
(0..u16_at(d, 4)).map(|i| 12 + 16 * i).find(|&r| &d[r..r + 4] == tag).map(|r| (u32_at(d, r + 8), u32_at(d, r + 12))).unwrap()
|
||||
};
|
||||
let (hmtx, _) = table(&data, b"hmtx");
|
||||
assert!(gid < u16_at(&data, table(&data, b"hhea").0 + 34), "the glyph has a full metrics record");
|
||||
let advance = u16_at(&data, hmtx + 4 * gid);
|
||||
data[hmtx + 4 * gid..hmtx + 4 * gid + 2].copy_from_slice(&((advance * 2) as u16).to_be_bytes());
|
||||
// The family name, in the name table's UTF-16 and single-byte records alike.
|
||||
let (name, len) = table(&data, b"name");
|
||||
let wide = |s: &str| s.encode_utf16().flat_map(u16::to_be_bytes).collect::<Vec<u8>>();
|
||||
for (from, to) in [(wide("Inter"), wide(FAMILY)), (b"Inter".to_vec(), FAMILY.as_bytes().to_vec())] {
|
||||
for i in name..name + len - from.len() {
|
||||
if data[i..i + from.len()] == from[..] {
|
||||
data[i..i + from.len()].copy_from_slice(&to);
|
||||
}
|
||||
}
|
||||
}
|
||||
wordcraft_fonts::FontDb::global().add_font(data);
|
||||
FAMILY
|
||||
}
|
||||
|
||||
/// The width of the first line of `text` set in the wide-nbsp font.
|
||||
fn line_width(text: &str) -> f32 {
|
||||
let chr = wordcraft_doc::CharProps { font: Some(wide_nbsp_family().into()), size: Some(12.0), ..Default::default() };
|
||||
let d = Document { body: vec![wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text(text, chr))], ..Document::new() };
|
||||
let mut c = LayoutCache::new();
|
||||
let l = layout(&d, &mut c, &LayoutOptions::default());
|
||||
for p in &l.pages {
|
||||
for it in &p.items {
|
||||
if let Placed::Lines { para, .. } = it {
|
||||
let xs = ¶.lines[0].xs;
|
||||
return xs[xs.len() - 1] - xs[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("paragraph not laid out");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_no_break_space_is_as_wide_as_a_space() {
|
||||
let face = wordcraft_fonts::word::resolve(wide_nbsp_family(), false, false).face;
|
||||
assert_eq!(face.family, wide_nbsp_family(), "the test font is installed under its own name");
|
||||
assert!(
|
||||
face.advance(face.glyph_for('\u{a0}')) > 1.9 * face.advance(face.glyph_for(' ')),
|
||||
"the font's own U+00A0 glyph is twice as wide as its space"
|
||||
);
|
||||
let nbsp = line_width(&format!("|{}|", "\u{a0}".repeat(10)));
|
||||
let space = line_width(&format!("|{}|", " ".repeat(10)));
|
||||
assert!((nbsp - space).abs() < 0.01, "ten no-break spaces measure {nbsp} pt, ten spaces {space} pt");
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user