mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-28 20:37:10 -06:00
This commit is contained in:
@@ -87,6 +87,34 @@ pub fn resolve(family: &str, bold: bool, italic: bool) -> Resolved {
|
||||
r
|
||||
}
|
||||
|
||||
/// The standard Unicode character for a Symbol or Wingdings character stored the way Word stores
|
||||
/// it, as the private-use code U+F000 + the font's own code (U+F0B7 is the Symbol bullet). Used
|
||||
/// when that font isn't installed: a substitute draws something else, or nothing, at the
|
||||
/// private-use code. `None` for other fonts and for codes not in the table.
|
||||
pub fn symbol_font_char(family: &str, c: char) -> Option<char> {
|
||||
let code = u32::from(c).checked_sub(0xF000)?;
|
||||
let u = match (family.to_ascii_lowercase().as_str(), code) {
|
||||
// Adobe Symbol encoding.
|
||||
("symbol", 0xA7) => '\u{2663}',
|
||||
("symbol", 0xA8) => '\u{2666}',
|
||||
("symbol", 0xA9) => '\u{2665}',
|
||||
("symbol", 0xAA) => '\u{2660}',
|
||||
("symbol", 0xAE) => '\u{2192}',
|
||||
("symbol", 0xB7) => '\u{2022}',
|
||||
("symbol", 0xD7) => '\u{22C5}',
|
||||
("symbol", 0xE0) => '\u{25CA}',
|
||||
// Wingdings characters common as list bullets.
|
||||
("wingdings", 0x6C) => '\u{25CF}',
|
||||
("wingdings", 0x6E) => '\u{25A0}',
|
||||
("wingdings", 0x76) => '\u{2756}',
|
||||
("wingdings", 0xA7) => '\u{25AA}',
|
||||
("wingdings", 0xD8) => '\u{27A2}',
|
||||
("wingdings", 0xFC) => '\u{2714}',
|
||||
_ => return None,
|
||||
};
|
||||
Some(u)
|
||||
}
|
||||
|
||||
/// Line metrics for a face in font units: (ascent, descent), both positive, the way word
|
||||
/// processors measure single line spacing (OS/2 usWinAscent/usWinDescent, falling back to the
|
||||
/// hhea values plus line gap).
|
||||
@@ -131,6 +159,19 @@ mod tests {
|
||||
assert!(a > 0.0 && d >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_font_chars_map_to_unicode() {
|
||||
assert_eq!(symbol_font_char("Symbol", '\u{F0B7}'), Some('\u{2022}'));
|
||||
assert_eq!(symbol_font_char("symbol", '\u{F0B7}'), Some('\u{2022}'));
|
||||
assert_eq!(symbol_font_char("Wingdings", '\u{F0A7}'), Some('\u{25AA}'));
|
||||
assert_eq!(symbol_font_char("Wingdings", '\u{F0D8}'), Some('\u{27A2}'));
|
||||
// Same code, other font; unknown codes; ordinary characters.
|
||||
assert_eq!(symbol_font_char("Arial", '\u{F0B7}'), None);
|
||||
assert_eq!(symbol_font_char("Symbol", '\u{F041}'), None);
|
||||
assert_eq!(symbol_font_char("Symbol", 'a'), None);
|
||||
assert_eq!(symbol_font_char("Symbol", '\u{10FFFF}'), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthesizes_missing_styles() {
|
||||
// JetBrains Mono ships only Regular here.
|
||||
|
||||
@@ -499,9 +499,20 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout {
|
||||
let label = env.label.as_ref().map(|(text, lvl)| {
|
||||
let lc = p.mark.clone().overlaid(&lvl.chr);
|
||||
let rc = resolve(&lc);
|
||||
// A Symbol/Wingdings bullet (U+F0B7…) without that font installed, or in a face that has
|
||||
// no glyph at the private-use code: draw the standard character instead, since a
|
||||
// substitute has another glyph, or none, there.
|
||||
let r = wordcraft_fonts::word::resolve(&rc.font, rc.bold, rc.italic);
|
||||
let text: String = text
|
||||
.chars()
|
||||
.map(|c| match wordcraft_fonts::word::symbol_font_char(&rc.font, c) {
|
||||
Some(u) if r.substituted || !r.face.covers(c) => u,
|
||||
_ => c,
|
||||
})
|
||||
.collect();
|
||||
let before_c = b.clusters.len();
|
||||
let before_g = b.glyphs.len();
|
||||
b.shape(text, 0, &rc, None);
|
||||
b.shape(&text, 0, &rc, None);
|
||||
let cl: Vec<Cluster> = b.clusters.drain(before_c..).collect();
|
||||
let mut glyphs = Vec::new();
|
||||
let mut x = 0.0;
|
||||
@@ -514,7 +525,7 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout {
|
||||
x += c.adv;
|
||||
}
|
||||
b.glyphs.truncate(before_g);
|
||||
Label { text: text.clone(), style, glyphs, x: 0.0, width: x }
|
||||
Label { text, style, glyphs, x: 0.0, width: x }
|
||||
});
|
||||
|
||||
let mut pl = ParaLayout {
|
||||
|
||||
@@ -120,6 +120,26 @@ fn lists_get_labels_and_indent() {
|
||||
assert!((x - 108.0).abs() < 0.5, "{x}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_font_bullet_without_the_font_draws_a_bullet() {
|
||||
// Word's default bullet: U+F0B7 in Symbol.
|
||||
let mut d = Document::from_text("one");
|
||||
let id = d.numbering.add_list(wordcraft_doc::ListKind::BulletChar('\u{F0B7}'));
|
||||
for a in &mut d.numbering.abstracts {
|
||||
if let Some(l) = a.levels.first_mut() {
|
||||
l.chr.font = Some("Symbol".into());
|
||||
}
|
||||
}
|
||||
d.format_paragraphs(&Pos::body(0, 0), &Pos::body(0, 0), &|p| p.numbering = Some(wordcraft_doc::props::NumRef { num: id, level: 0 })).unwrap();
|
||||
let l = lay(&d);
|
||||
let label = l.pages[0].items.iter().find_map(|it| if let Placed::Lines { para, .. } = it { para.label.clone() } else { None }).unwrap();
|
||||
// Where a real Symbol font with the private-use code is installed (Windows), the code stays.
|
||||
let symbol = wordcraft_fonts::word::resolve("Symbol", false, false);
|
||||
let expected = if symbol.substituted || !symbol.face.covers('\u{F0B7}') { "\u{2022}" } else { "\u{F0B7}" };
|
||||
assert_eq!(label.text, expected);
|
||||
assert_eq!(label.glyphs.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_lay_out_cells() {
|
||||
let mut d = Document::from_text("before\nafter");
|
||||
|
||||
Reference in New Issue
Block a user