mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-30 21:47:00 -06:00
Text: a space after a symbol-font character keeps the run's font
PowerPoint stores an arrow typed in a Wingdings run as U+F0E0 with <a:sym typeface="Wingdings"/> on the whole run, and draws only the symbol code in Wingdings: the space after it is the run's own font. A synthetic deck with "→ Only …" (U+F0E0, a space, words) in one run with a:sym, and the same with a:sym on the arrow's run alone, exported as PDF, draws both lines alike. DeckCraft kept the space in the arrow's face, since spaces, digits and punctuation stay in the face of the text before them, and Wingdings' space is about four times a text font's: the words started 20 pt further right. A neutral character no longer stays in a symbol font's face (is_symbol_font); it takes the run's font like any other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aaf6c1c38a
commit
0865152e86
@@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
|
||||
| Date | What landed |
|
||||
|---|---|
|
||||
| 2026-10-26 | A space, digit or punctuation after a symbol-font character (`a:sym`, e.g. a Wingdings arrow typed in a run) draws in the run's own font, as PowerPoint does; it stayed in the symbol font, whose space is about four times as wide (synthetic probe) |
|
||||
| 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once |
|
||||
| 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off |
|
||||
| 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |
|
||||
@@ -312,6 +313,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-15 | trivial | Progress log: PPTX trigger click-step round trip. Feature scores and milestone names unchanged |
|
||||
| 2026-10-16 | trivial | Progress log: PNG export with a transparent background (#784) |
|
||||
| 2026-10-16 | trivial | Progress log: a `.ppsx` from the system starts the slide show (#862) |
|
||||
| 2026-10-26 | trivial | Progress log: spaces after symbol-font characters |
|
||||
| 2026-10-24 | trivial | Progress log: picture and embedded object fills |
|
||||
| 2026-10-22 | trivial | Progress log: table text takes the master's other-text style |
|
||||
| 2026-10-16 | trivial | Progress log: responsive mouse-wheel navigation on the fitted canvas |
|
||||
|
||||
@@ -867,7 +867,9 @@ fn shape_cells(
|
||||
let Some(st) = styles.get(si) else { continue };
|
||||
let (sc, slot) = scripts.get(a).copied().unwrap_or((None, FontSlot::Latin));
|
||||
// Spaces, digits and punctuation stay in the face of the text before them when it can
|
||||
// draw them, so a phrase isn't cut into items at every space.
|
||||
// draw them, so a phrase isn't cut into items at every space; not in a symbol font
|
||||
// (`a:sym`), which PowerPoint uses for the symbol codes alone (Wingdings' space is about
|
||||
// four times a text font's).
|
||||
// Emoji, and pictographs asked to look like text (U+FE0E), pick their own face.
|
||||
let neutral =
|
||||
cluster.iter().all(|c| script::script_of(*c).is_none()) && !deckcraft_fonts::is_emoji_cluster(cluster) && !cluster.contains(&'\u{FE0E}');
|
||||
@@ -884,7 +886,11 @@ fn shape_cells(
|
||||
let face = match (sym_face, prev.as_ref()) {
|
||||
(Some(f), _) => f,
|
||||
(None, Some(p))
|
||||
if neutral && !p.is_color() && deckcraft_fonts::covers_cluster(p, cluster) && char_style.get(a.wrapping_sub(1)) == Some(&si) =>
|
||||
if neutral
|
||||
&& !p.is_color()
|
||||
&& !is_symbol_font(&p.family)
|
||||
&& deckcraft_fonts::covers_cluster(p, cluster)
|
||||
&& char_style.get(a.wrapping_sub(1)) == Some(&si) =>
|
||||
{
|
||||
p.clone()
|
||||
}
|
||||
|
||||
@@ -1255,6 +1255,43 @@ fn symbol_font_characters_draw_in_the_sym_font() {
|
||||
assert!(families.iter().all(|(f, c)| c.0 == 1 || !f.eq_ignore_ascii_case(symbol)), "{families:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_space_after_a_symbol_font_character_keeps_the_runs_font() {
|
||||
// PowerPoint stores "→ Only" typed in a Wingdings run as U+F0E0, a space and the words, all
|
||||
// with `<a:sym typeface="Wingdings"/>`: only the symbol code draws in Wingdings. The space
|
||||
// stayed in the arrow's face, and Wingdings' space is about four times as wide. Skipped
|
||||
// without Wingdings.
|
||||
let db = FontDb::global();
|
||||
if !db.has_family("Wingdings") || !covers_symbol(&db.face("Wingdings", "Regular"), '\u{F0E0}') {
|
||||
eprintln!("skipped: no installed Wingdings face draws U+F0E0");
|
||||
return;
|
||||
}
|
||||
let laid = |split: bool| {
|
||||
let (p, mut sh) = setup("\u{F0E0} Only", 900.0, 300.0);
|
||||
let para = &mut sh.text.as_mut().unwrap().paragraphs[0];
|
||||
para.props.bullet = Some(Bullet::None);
|
||||
para.runs[0].props.font = Some("Arial".into());
|
||||
para.runs[0].props.font_sym = Some("Wingdings".into());
|
||||
if split {
|
||||
// The arrow alone carries the symbol font.
|
||||
let mut rest = para.runs[0].clone();
|
||||
rest.text = " Only".into();
|
||||
rest.props.font_sym = None;
|
||||
para.runs[0].text = "\u{F0E0}".into();
|
||||
para.runs.push(rest);
|
||||
}
|
||||
lay(&p, &sh, 900.0, 300.0)
|
||||
};
|
||||
let whole = laid(false);
|
||||
let families: Vec<(String, (usize, usize))> = whole.runs.iter().map(|r| (r.face.family.clone(), r.chars)).collect();
|
||||
assert!(families.iter().any(|(f, c)| f.eq_ignore_ascii_case("Wingdings") && c.0 == 0), "the arrow: {families:?}");
|
||||
let in_wingdings = |i: usize| families.iter().any(|(f, c)| (c.0..c.1).contains(&i) && f.eq_ignore_ascii_case("Wingdings"));
|
||||
assert!(!in_wingdings(1) && !in_wingdings(2), "the space and words: {families:?}");
|
||||
// "Only" starts where it does when the arrow alone is in Wingdings.
|
||||
let o = |l: &TextLayout| l.lines[0].caret_x[2];
|
||||
assert!((o(&whole) - o(&laid(true))).abs() < 0.01, "{} vs {}", o(&whole), o(&laid(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn small_capitals_draw_smaller_than_the_capitals_beside_them() {
|
||||
let (p, mut sh) = setup("HxHx", 2000.0, 300.0);
|
||||
|
||||
Reference in New Issue
Block a user