mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor sub- and superscript substitution (#5120)
This commit is contained in:
parent
bb39d8f10a
commit
142c9dff49
@ -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,
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user