Compare commits
2 Commits
main
...
fix/symbol
| Author | SHA1 | Date |
|---|---|---|
|
|
da5332b5df | |
|
|
29ed409282 |
|
|
@ -130,6 +130,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
|||
## Progress log
|
||||
|
||||
| Date | What landed |
|
||||
| 2026-10-12 | List bullets in Symbol and Wingdings stored as bare codes (Microsoft's spec documents use `§` in Wingdings) draw Word's square or dot, not `§` |
|
||||
| 2026-10-11 | Hide White Space (#645): `view.hideWhiteSpace` (View › Show checkbox, or double-click the gap between pages) collapses margins, headers and footers between pages to a thin line in Print Layout; screen only, page count unchanged |
|
||||
| 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) |
|
||||
|---|---|
|
||||
|
|
|
|||
|
|
@ -208,11 +208,16 @@ pub fn resolve(family: &str, bold: bool, italic: bool) -> Resolved {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
/// it, as the private-use code U+F000 + the font's own code (U+F0B7 is the Symbol bullet), or as
|
||||
/// the bare code U+0020–U+00FF (a list level's `§` in Wingdings is its small square bullet, as Word
|
||||
/// draws it). Used when that font isn't installed: a substitute draws something else, or nothing,
|
||||
/// there. `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 code = match u32::from(c) {
|
||||
u @ 0xF020..=0xF0FF => u - 0xF000,
|
||||
u @ 0x20..=0xFF => u,
|
||||
_ => return None,
|
||||
};
|
||||
let u = match (family.to_ascii_lowercase().as_str(), code) {
|
||||
// Adobe Symbol encoding.
|
||||
("symbol", 0xA7) => '\u{2663}',
|
||||
|
|
@ -311,6 +316,12 @@ mod tests {
|
|||
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}'));
|
||||
// The same code stored bare, as Microsoft's own specification documents do.
|
||||
assert_eq!(symbol_font_char("Wingdings", '\u{A7}'), Some('\u{25AA}'));
|
||||
assert_eq!(symbol_font_char("Symbol", '\u{B7}'), Some('\u{2022}'));
|
||||
assert_eq!(symbol_font_char("Calibri", '\u{A7}'), None, "only symbol fonts");
|
||||
assert_eq!(symbol_font_char("Wingdings", 'A'), None, "codes outside the table stay");
|
||||
assert_eq!(symbol_font_char("Wingdings", '\u{100}'), None);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1245,9 +1245,15 @@ pub fn layout_para(p: &Paragraph, env: &ParaEnv) -> ParaLayout {
|
|||
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,
|
||||
.map(|c| {
|
||||
// A bare code (`§` = 0xA7) in a symbol font that is installed: its glyph sits at the
|
||||
// private-use code (U+F0A7); drawn from that font, the line keeps Word's metrics.
|
||||
let private = (u32::from(c) < 0x100).then(|| char::from_u32(0xF000 + u32::from(c))).flatten();
|
||||
match (private, wordcraft_fonts::word::symbol_font_char(&rc.font, c)) {
|
||||
(Some(pc), Some(_)) if !r.substituted && !r.face.covers(c) && r.face.covers(pc) => pc,
|
||||
(_, Some(u)) if r.substituted || !r.face.covers(c) => u,
|
||||
_ => c,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let before_c = b.clusters.len();
|
||||
|
|
|
|||
|
|
@ -3948,3 +3948,32 @@ fn footer_page_total_needs_one_body_pass() {
|
|||
p.insert_object(n, field(), &Default::default()).unwrap();
|
||||
assert!(matches!(first(&d), Pass::Again(t) if t.pages == 3));
|
||||
}
|
||||
|
||||
/// A Wingdings bullet stored as its bare code (`§` = 0xA7, as Microsoft's specification documents
|
||||
/// store it) draws Wingdings' small square, as in Word, not a section sign.
|
||||
#[test]
|
||||
fn bare_code_wingdings_bullet_draws_its_square() {
|
||||
let mut d = Document::from_text("one");
|
||||
let id = d.numbering.add_list(wordcraft_doc::ListKind::BulletChar('\u{A7}'));
|
||||
for a in &mut d.numbering.abstracts {
|
||||
if let Some(l) = a.levels.first_mut() {
|
||||
l.chr.font = Some("Wingdings".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 Wingdings is installed (Windows) its own glyph at the private-use code is drawn,
|
||||
// which keeps Word's line height; elsewhere the standard square.
|
||||
let wd = wordcraft_fonts::word::resolve("Wingdings", false, false);
|
||||
let expected = if wd.substituted {
|
||||
"\u{25AA}"
|
||||
} else if wd.face.covers('\u{A7}') {
|
||||
"\u{A7}"
|
||||
} else if wd.face.covers('\u{F0A7}') {
|
||||
"\u{F0A7}"
|
||||
} else {
|
||||
"\u{25AA}"
|
||||
};
|
||||
assert_eq!(label.text, expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ font can't match Word's metrics.
|
|||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | trivial | Symbol and Wingdings list bullets stored as bare codes (U+0020–U+00FF) draw their standard characters, as Word does |
|
||||
| 2026-10-11 | trivial | Chinese documents (#534): indents in characters, a page-anchored floating table after a full page moves to the next page, no blank page from a page break at the bottom of a full page |
|
||||
| 2026-10-11 | trivial | East Asian list number formats (一、二、三, 第一章, ①); list labels whose characters come from two fonts draw each part with its own font (the digits of "1、" no longer show as other letters); a list level without indents leaves the label at the paragraph edge (#534) |
|
||||
| 2026-10-11 | trivial | Vertical sections: `w:textDirection` in `w:sectPr`, upright East Asian glyphs (#491) |
|
||||
|
|
|
|||
Loading…
Reference in New Issue