mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
text: add Symbol euro and Apple alternatives; keep covered bullets
Adobe symbol.txt also maps 0xA0 to the euro sign (U+20AC), which symbol_unicode lacked. Apple's Symbol maps a few codes to different Unicode than Adobe (0xA4 to U+2044, 0xE1/0xF1 to U+3008/U+3009), so symbol_font_draw now tries those alternatives after the Adobe character when the installed face doesn't cover it. The Adobe aliases for 0x44/0x57/0x6D (U+2206/U+2126/U+00B5) are not Apple alternatives and stay out. symbol_font_char masked every bullet character to its low byte before checking direct coverage, so a 'bullet' (U+2022) bullet drew an unrelated Symbol code. It now maps only symbol-font codes (low byte or U+F0xx) and falls back to the original character when the face covers it.
This commit is contained in:
+38
-18
@@ -1168,13 +1168,14 @@ fn symbol_map_char(face: &FontFace, ch: char, sym: Option<&str>) -> Option<char>
|
||||
}
|
||||
|
||||
/// The real-Unicode character the Symbol font's code `code` (0x00–0xFF) maps to, in the Adobe
|
||||
/// Symbol encoding (Unicode, Inc.'s `MAPPINGS/VENDORS/ADOBE/symbol.txt`; Apple's Mac OS Symbol is
|
||||
/// laid out identically): the codes a document stores as U+F000 + code draw the same glyphs at
|
||||
/// these Unicode points. Apple's "Symbol" on macOS has no private-use mapping at all — it maps the
|
||||
/// glyphs to these real characters instead — so a document's U+F0xx symbol characters must be
|
||||
/// remapped to them there (PowerPoint does the same against whatever the installed Symbol font
|
||||
/// covers). Only the non-ASCII, non-CUS entries are listed: printable ASCII passes through as its
|
||||
/// own code, and the CUS (Corporate Use Subarea) fragment glyphs have no standard Unicode point.
|
||||
/// Symbol encoding (Unicode, Inc.'s `MAPPINGS/VENDORS/ADOBE/symbol.txt`; Apple's Mac OS Symbol
|
||||
/// shares this layout, with a few alternative Unicode choices — see `symbol_unicode_alts`): the
|
||||
/// codes a document stores as U+F000 + code draw the same glyphs at these Unicode points. Apple's
|
||||
/// "Symbol" on macOS has no private-use mapping at all — it maps the glyphs to these real characters
|
||||
/// instead — so a document's U+F0xx symbol characters must be remapped to them there (PowerPoint
|
||||
/// does the same against whatever the installed Symbol font covers). Only the non-ASCII, non-CUS
|
||||
/// entries are listed: printable ASCII passes through as its own code, and the CUS (Corporate Use
|
||||
/// Subarea) fragment glyphs have no standard Unicode point.
|
||||
const fn symbol_unicode(code: u32) -> Option<u32> {
|
||||
Some(match code {
|
||||
// 0x20–0x7E: the few mathematical ones among the ASCII-range characters.
|
||||
@@ -1241,7 +1242,8 @@ const fn symbol_unicode(code: u32) -> Option<u32> {
|
||||
0x78 => 0x03BE,
|
||||
0x79 => 0x03C8,
|
||||
0x7A => 0x03B6,
|
||||
// 0xA1–0xAF: Greek upsilon with hook, primes, math, suits and arrows.
|
||||
// 0xA0–0xAF: euro, Greek upsilon with hook, primes, math, suits and arrows.
|
||||
0xA0 => 0x20AC,
|
||||
0xA1 => 0x03D2,
|
||||
0xA2 => 0x2032,
|
||||
0xA3 => 0x2264,
|
||||
@@ -1315,11 +1317,25 @@ const fn symbol_unicode(code: u32) -> Option<u32> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Apple's Mac OS Symbol (which shares the Adobe layout) diverges from the Adobe table on a few
|
||||
/// codes: it maps the fraction slash to U+2044 and the angle brackets to U+3008/U+3009 instead of
|
||||
/// Adobe's U+2215/U+2329/U+232A. `symbol_font_draw` tries these alternatives after the Adobe
|
||||
/// mapping when the installed face doesn't cover the Adobe character. The additional Adobe aliases
|
||||
/// for 0x44/0x57/0x6D (U+2206/U+2126/U+00B5) are not Apple alternatives and are deliberately absent.
|
||||
const fn symbol_unicode_alts(code: u32) -> &'static [u32] {
|
||||
match code {
|
||||
0xA4 => &[0x2044],
|
||||
0xE1 => &[0x3008],
|
||||
0xF1 => &[0x3009],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// The character the symbol font draws the code `code` at, when the installed `face` is that font:
|
||||
/// its private-use code U+F000 + code, or, when the face has no private-use mapping (Apple's
|
||||
/// "Symbol" maps its glyphs to real Unicode), the Adobe Symbol encoding's real character. Printable
|
||||
/// ASCII with no table entry (the `< = > ?` codes among them) passes through as its own character
|
||||
/// when the face covers it.
|
||||
/// "Symbol" maps its glyphs to real Unicode), the Adobe Symbol encoding's real character (then its
|
||||
/// Apple alternative, then, for printable ASCII with no table entry — the `< = > ?` codes among
|
||||
/// them — the ASCII character itself) when the face covers it.
|
||||
fn symbol_font_draw(face: &FontFace, code: u32) -> Option<char> {
|
||||
let code = code & 0xFF;
|
||||
let pua = char::from_u32(0xF000 | code)?;
|
||||
@@ -1327,11 +1343,7 @@ fn symbol_font_draw(face: &FontFace, code: u32) -> Option<char> {
|
||||
return Some(pua);
|
||||
}
|
||||
if let Some(real) = symbol_unicode(code) {
|
||||
let real = char::from_u32(real)?;
|
||||
if face.covers(real) {
|
||||
return Some(real);
|
||||
}
|
||||
return None;
|
||||
return std::iter::once(real).chain(symbol_unicode_alts(code).iter().copied()).filter_map(char::from_u32).find(|c| face.covers(*c));
|
||||
}
|
||||
// 0x20–0x7E with no table entry draw the ASCII character itself (the Symbol font's glyphs for
|
||||
// "< = > ?" etc. sit at their own ASCII codes, not at a remapped Unicode point).
|
||||
@@ -1352,8 +1364,16 @@ fn symbol_font_char(face: &FontFace, c: &str, font: Option<&str>) -> Option<char
|
||||
return None;
|
||||
}
|
||||
let first = c.chars().next()?;
|
||||
let code = (first as u32) & 0xFF;
|
||||
symbol_font_draw(face, code).or_else(|| face.covers(first).then_some(first))
|
||||
let cp = first as u32;
|
||||
// Only symbol-font codes (low byte, or the U+F000 + code private-use form) take the mapping.
|
||||
// A bullet whose character is a plain Unicode codepoint (e.g. U+2022 •) draws its own glyph
|
||||
// when the face covers it, never a low-byte remap to an unrelated Symbol code.
|
||||
if (cp < 0x100 || (0xF000..=0xF0FF).contains(&cp))
|
||||
&& let Some(c) = symbol_font_draw(face, cp & 0xFF)
|
||||
{
|
||||
return Some(c);
|
||||
}
|
||||
face.covers(first).then_some(first)
|
||||
}
|
||||
|
||||
/// The character that draws `c`, a character of text set in the symbol font `font` (Wingdings,
|
||||
|
||||
@@ -937,6 +937,27 @@ fn an_installed_symbol_font_draws_its_own_bullet() {
|
||||
assert_eq!(symbol_font_char(&face, "\u{ea}", Some("Arial")), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_font_char_keeps_covered_non_symbol_bullets() {
|
||||
// `Bullet::Char.char` is unrestricted text; the PPTX reader stores `buChar` directly. A bullet
|
||||
// such as U+2022 • must draw its own covered glyph, never a low-byte remap (0x22 → FOR ALL)
|
||||
// to an unrelated Symbol code. Skipped when the face doesn't cover U+2022.
|
||||
let db = FontDb::global();
|
||||
if !db.has_family("Symbol") {
|
||||
eprintln!("skipped: no Symbol font installed");
|
||||
return;
|
||||
}
|
||||
let face = db.face("Symbol", "Regular");
|
||||
if !face.covers('•') {
|
||||
eprintln!("skipped: installed Symbol face lacks U+2022");
|
||||
return;
|
||||
}
|
||||
assert_eq!(symbol_font_char(&face, "•", Some("Symbol")), Some('•'), "U+2022 draws itself, not a low-byte remap");
|
||||
// A real symbol code still takes the mapping.
|
||||
let f0de = String::from_utf8(vec![0xEF, 0x83, 0x9E]).unwrap(); // U+F0DE
|
||||
assert_eq!(symbol_font_char(&face, &f0de, Some("Symbol")), symbol_font_draw(&face, 0xDE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_char_maps_symbol_font_codes() {
|
||||
// Inter is not a symbol font, so the codes map to look-alikes.
|
||||
@@ -998,6 +1019,44 @@ fn symbol_font_draw_falls_back_to_ascii() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_font_draw_tries_apple_alternatives() {
|
||||
// Apple's Symbol maps a few codes differently from Adobe: 0xA4 (fraction slash) to U+2044
|
||||
// instead of Adobe's U+2215, and 0xE1/0xF1 (angle brackets) to U+3008/U+3009 instead of
|
||||
// U+2329/U+232A. When the installed face doesn't cover the Adobe character, the alternative is
|
||||
// tried before giving up. Skipped without an installed Symbol face.
|
||||
let db = FontDb::global();
|
||||
if !db.has_family("Symbol") {
|
||||
eprintln!("skipped: no Symbol font installed");
|
||||
return;
|
||||
}
|
||||
let face = db.face("Symbol", "Regular");
|
||||
for &(code, adobe, alt) in &[(0xA4, '\u{2215}', '\u{2044}'), (0xE1, '\u{2329}', '\u{3008}'), (0xF1, '\u{232A}', '\u{3009}')] {
|
||||
let pua = char::from_u32(0xF000 | code).unwrap();
|
||||
let drawn = symbol_font_draw(&face, code);
|
||||
if face.covers(pua) {
|
||||
assert_eq!(drawn, Some(pua), "the face's private-use glyph wins for 0x{code:02X}");
|
||||
} else if face.covers(adobe) {
|
||||
assert_eq!(drawn, Some(adobe), "Adobe U+{:04X} wins for 0x{code:02X} when covered", adobe as u32);
|
||||
} else {
|
||||
assert_eq!(drawn, Some(alt), "Apple alternative U+{:04X} is tried for 0x{code:02X}", alt as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbol_unicode_maps_the_euro() {
|
||||
// Adobe symbol.txt: 20AC A0 # EURO SIGN. The euro is the one currency symbol in the encoding.
|
||||
assert_eq!(symbol_unicode(0xA0), Some(0x20AC));
|
||||
// Apple alternatives are tried in order for 0xA4/0xE1/0xF1.
|
||||
assert_eq!(symbol_unicode_alts(0xA4), &[0x2044u32]);
|
||||
assert_eq!(symbol_unicode_alts(0xE1), &[0x3008u32]);
|
||||
assert_eq!(symbol_unicode_alts(0xF1), &[0x3009u32]);
|
||||
assert_eq!(symbol_unicode_alts(0x44), &[] as &[u32]);
|
||||
assert_eq!(symbol_unicode_alts(0x57), &[] as &[u32]);
|
||||
assert_eq!(symbol_unicode_alts(0x6D), &[] as &[u32]);
|
||||
}
|
||||
|
||||
/// Every entry of the Adobe Symbol encoding table, in full. Pure and platform-independent: the
|
||||
/// table maps a Symbol code to its real Unicode character regardless of any installed font, so this
|
||||
/// runs on every machine. Guarded against typos in `symbol_unicode` (e.g. 0x40 must be U+2245, the
|
||||
@@ -1019,8 +1078,8 @@ fn symbol_unicode_table_covers_the_adobe_encoding() {
|
||||
(0x61, 0x03B1), (0x62, 0x03B2), (0x63, 0x03C7), (0x64, 0x03B4), (0x65, 0x03B5), (0x66, 0x03C6), (0x67, 0x03B3), (0x68, 0x03B7), (0x69, 0x03B9), (0x6A, 0x03D5),
|
||||
(0x6B, 0x03BA), (0x6C, 0x03BB), (0x6D, 0x03BC), (0x6E, 0x03BD), (0x6F, 0x03BF), (0x70, 0x03C0), (0x71, 0x03B8), (0x72, 0x03C1), (0x73, 0x03C3), (0x74, 0x03C4),
|
||||
(0x75, 0x03C5), (0x76, 0x03D6), (0x77, 0x03C9), (0x78, 0x03BE), (0x79, 0x03C8), (0x7A, 0x03B6),
|
||||
// 0xA1–0xAF: Greek upsilon with hook, primes, math, suits and arrows.
|
||||
(0xA1, 0x03D2), (0xA2, 0x2032), (0xA3, 0x2264), (0xA4, 0x2215), (0xA5, 0x221E), (0xA6, 0x0192), (0xA7, 0x2663), (0xA8, 0x2666), (0xA9, 0x2665), (0xAA, 0x2660),
|
||||
// 0xA0–0xAF: euro, Greek upsilon with hook, primes, math, suits and arrows.
|
||||
(0xA0, 0x20AC), (0xA1, 0x03D2), (0xA2, 0x2032), (0xA3, 0x2264), (0xA4, 0x2215), (0xA5, 0x221E), (0xA6, 0x0192), (0xA7, 0x2663), (0xA8, 0x2666), (0xA9, 0x2665), (0xAA, 0x2660),
|
||||
(0xAB, 0x2194), (0xAC, 0x2190), (0xAD, 0x2191), (0xAE, 0x2192), (0xAF, 0x2193),
|
||||
// 0xB0–0xBC: degree, plus-minus, double prime, math and ellipsis.
|
||||
(0xB0, 0x00B0), (0xB1, 0x00B1), (0xB2, 0x2033), (0xB3, 0x2265), (0xB4, 0x00D7), (0xB5, 0x221D), (0xB6, 0x2202), (0xB7, 0x2022), (0xB8, 0x00F7), (0xB9, 0x2260),
|
||||
|
||||
Reference in New Issue
Block a user