Refactor sub- and superscript substitution (#5120)

This commit is contained in:
Malo 2024-10-07 12:57:20 +02:00 committed by GitHub
parent bb39d8f10a
commit 142c9dff49
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -51,19 +51,18 @@ impl Show for Packed<SubElem> {
#[typst_macros::time(name = "sub", span = self.span())] #[typst_macros::time(name = "sub", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let body = self.body().clone(); let body = self.body().clone();
let mut transformed = None;
if self.typographic(styles) { if self.typographic(styles) {
if let Some(text) = search_text(&body, true) { if let Some(text) = convert_script(&body, true) {
if is_shapable(engine, &text, styles) { if is_shapable(engine, &text, styles) {
transformed = Some(TextElem::packed(text)); return Ok(TextElem::packed(text));
} }
} }
}; };
Ok(transformed.unwrap_or_else(|| { Ok(body
body.styled(TextElem::set_baseline(self.baseline(styles))) .styled(TextElem::set_baseline(self.baseline(styles)))
.styled(TextElem::set_size(self.size(styles))) .styled(TextElem::set_size(self.size(styles))))
}))
} }
} }
@ -111,38 +110,38 @@ impl Show for Packed<SuperElem> {
#[typst_macros::time(name = "super", span = self.span())] #[typst_macros::time(name = "super", span = self.span())]
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let body = self.body().clone(); let body = self.body().clone();
let mut transformed = None;
if self.typographic(styles) { if self.typographic(styles) {
if let Some(text) = search_text(&body, false) { if let Some(text) = convert_script(&body, false) {
if is_shapable(engine, &text, styles) { if is_shapable(engine, &text, styles) {
transformed = Some(TextElem::packed(text)); return Ok(TextElem::packed(text));
} }
} }
}; };
Ok(transformed.unwrap_or_else(|| { Ok(body
body.styled(TextElem::set_baseline(self.baseline(styles))) .styled(TextElem::set_baseline(self.baseline(styles)))
.styled(TextElem::set_size(self.size(styles))) .styled(TextElem::set_size(self.size(styles))))
}))
} }
} }
/// Find and transform the text contained in `content` to the given script kind /// Find and transform the text contained in `content` to the given script kind
/// if and only if it only consists of `Text`, `Space`, and `Empty` leaves. /// if and only if it only consists of `Text`, `Space`, and `Empty` leaves.
fn search_text(content: &Content, sub: bool) -> Option<EcoString> { fn convert_script(content: &Content, sub: bool) -> Option<EcoString> {
if content.is::<SpaceElem>() { if content.is::<SpaceElem>() {
Some(' '.into()) Some(' '.into())
} else if let Some(elem) = content.to_packed::<TextElem>() { } else if let Some(elem) = content.to_packed::<TextElem>() {
convert_script(elem.text(), sub) if sub {
} else if let Some(sequence) = content.to_packed::<SequenceElem>() { elem.text().chars().map(to_subscript_codepoint).collect()
let mut full = EcoString::new(); } else {
for item in &sequence.children { elem.text().chars().map(to_superscript_codepoint).collect()
match search_text(item, sub) {
Some(text) => full.push_str(&text),
None => return None,
}
} }
Some(full) } else if let Some(sequence) = content.to_packed::<SequenceElem>() {
sequence
.children
.iter()
.map(|item| convert_script(item, sub))
.collect()
} else { } else {
None None
} }
@ -165,65 +164,47 @@ fn is_shapable(engine: &Engine, text: &str, styles: StyleChain) -> bool {
false false
} }
/// Convert a string to sub- or superscript codepoints if all characters
/// can be mapped to such a codepoint.
fn convert_script(text: &str, sub: bool) -> Option<EcoString> {
let mut result = EcoString::with_capacity(text.len());
let converter = if sub { to_subscript_codepoint } else { to_superscript_codepoint };
for c in text.chars() {
match converter(c) {
Some(c) => result.push(c),
None => return None,
}
}
Some(result)
}
/// Convert a character to its corresponding Unicode superscript. /// Convert a character to its corresponding Unicode superscript.
fn to_superscript_codepoint(c: char) -> Option<char> { fn to_superscript_codepoint(c: char) -> Option<char> {
char::from_u32(match c { match c {
'0' => 0x2070, '1' => Some('¹'),
'1' => 0x00B9, '2' => Some('²'),
'2' => 0x00B2, '3' => Some('³'),
'3' => 0x00B3, '0' | '4'..='9' => char::from_u32(c as u32 - '0' as u32 + '⁰' as u32),
'4'..='9' => 0x2070 + (c as u32 + 4 - '4' as u32), '+' => Some('⁺'),
'+' => 0x207A, '' => Some('⁻'),
'-' => 0x207B, '=' => Some('⁼'),
'=' => 0x207C, '(' => Some('⁽'),
'(' => 0x207D, ')' => Some('⁾'),
')' => 0x207E, 'n' => Some('ⁿ'),
'n' => 0x207F, 'i' => Some('ⁱ'),
'i' => 0x2071, ' ' => Some(' '),
' ' => 0x0020, _ => None,
_ => return None, }
})
} }
/// Convert a character to its corresponding Unicode subscript. /// Convert a character to its corresponding Unicode subscript.
fn to_subscript_codepoint(c: char) -> Option<char> { fn to_subscript_codepoint(c: char) -> Option<char> {
char::from_u32(match c { match c {
'0' => 0x2080, '0'..='9' => char::from_u32(c as u32 - '0' as u32 + '₀' as u32),
'1'..='9' => 0x2080 + (c as u32 - '0' as u32), '+' => Some('₊'),
'+' => 0x208A, '' => Some('₋'),
'-' => 0x208B, '=' => Some('₌'),
'=' => 0x208C, '(' => Some('₍'),
'(' => 0x208D, ')' => Some('₎'),
')' => 0x208E, 'a' => Some('ₐ'),
'a' => 0x2090, 'e' => Some('ₑ'),
'e' => 0x2091, 'o' => Some('ₒ'),
'o' => 0x2092, 'x' => Some('ₓ'),
'x' => 0x2093, 'h' => Some('ₕ'),
'h' => 0x2095, 'k' => Some('ₖ'),
'k' => 0x2096, 'l' => Some('ₗ'),
'l' => 0x2097, 'm' => Some('ₘ'),
'm' => 0x2098, 'n' => Some('ₙ'),
'n' => 0x2099, 'p' => Some('ₚ'),
'p' => 0x209A, 's' => Some('ₛ'),
's' => 0x209B, 't' => Some('ₜ'),
't' => 0x209C, ' ' => Some(' '),
' ' => 0x0020, _ => None,
_ => return None, }
})
} }