Add SymbolElem to realization

This commit is contained in:
Ian Wrzesinski 2024-07-29 00:25:03 -05:00
parent fecdc39846
commit 7838da02ec
4 changed files with 48 additions and 13 deletions

View File

@ -221,7 +221,7 @@ impl<'a, 'x, 'y, 'z, 's> Grouped<'a, 'x, 'y, 'z, 's> {
/// Handles an arbitrary piece of content during realization. /// Handles an arbitrary piece of content during realization.
fn visit<'a>( fn visit<'a>(
s: &mut State<'a, '_, '_, '_>, s: &mut State<'a, '_, '_, '_>,
mut content: &'a Content, content: &'a Content,
styles: StyleChain<'a>, styles: StyleChain<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
// Tags can always simply be pushed. // Tags can always simply be pushed.
@ -230,12 +230,6 @@ fn visit<'a>(
return Ok(()); return Ok(());
} }
if let Some(elem) = content.to_packed::<SymbolElem>() {
// This is a hack to avoid affecting layout that will be replaced in a
// later commit.
content = Box::leak(Box::new(TextElem::packed(elem.text.to_string())));
}
// Transformations for math content based on the realization kind. Needs // Transformations for math content based on the realization kind. Needs
// to happen before show rules. // to happen before show rules.
if visit_math_rules(s, content, styles)? { if visit_math_rules(s, content, styles)? {
@ -247,7 +241,7 @@ fn visit<'a>(
return Ok(()); return Ok(());
} }
// Recurse into sequences. Styled elements and sequences can currently also // Recurse into sequences. Styled elements and sequences can currently also
// have labels, so this needs to happen before they are handled. // have labels, so this needs to happen before they are handled.
if let Some(sequence) = content.to_packed::<SequenceElem>() { if let Some(sequence) = content.to_packed::<SequenceElem>() {
for elem in &sequence.children { for elem in &sequence.children {
@ -301,7 +295,14 @@ fn visit_math_rules<'a>(
// In normal realization, we apply regex show rules to consecutive // In normal realization, we apply regex show rules to consecutive
// textual elements via `TEXTUAL` grouping. However, in math, this is // textual elements via `TEXTUAL` grouping. However, in math, this is
// not desirable, so we just do it on a per-element basis. // not desirable, so we just do it on a per-element basis.
if let Some(elem) = content.to_packed::<TextElem>() { if let Some(elem) = content.to_packed::<SymbolElem>() {
if let Some(m) =
find_regex_match_in_str(elem.text.encode_utf8(&mut [0; 4]), styles)
{
visit_regex_match(s, &[(content, styles)], m)?;
return Ok(true);
}
} else if let Some(elem) = content.to_packed::<TextElem>() {
if let Some(m) = find_regex_match_in_str(&elem.text, styles) { if let Some(m) = find_regex_match_in_str(&elem.text, styles) {
visit_regex_match(s, &[(content, styles)], m)?; visit_regex_match(s, &[(content, styles)], m)?;
return Ok(true); return Ok(true);
@ -314,6 +315,14 @@ fn visit_math_rules<'a>(
visit(s, s.store(eq), styles)?; visit(s, s.store(eq), styles)?;
return Ok(true); return Ok(true);
} }
// Symbols in non-math content transparently convert to `TextElem` so we
// don't have to handle them in non-math layout.
if let Some(elem) = content.to_packed::<SymbolElem>() {
let text = TextElem::packed(elem.text).spanned(elem.span());
visit(s, s.store(text), styles)?;
return Ok(true);
}
} }
Ok(false) Ok(false)
@ -792,7 +801,7 @@ static HTML_DOCUMENT_RULES: &[&GroupingRule] =
/// Grouping rules used in HTML fragment realization. /// Grouping rules used in HTML fragment realization.
static HTML_FRAGMENT_RULES: &[&GroupingRule] = &[&TEXTUAL, &CITES, &LIST, &ENUM, &TERMS]; static HTML_FRAGMENT_RULES: &[&GroupingRule] = &[&TEXTUAL, &CITES, &LIST, &ENUM, &TERMS];
/// Grouping rules used in math realizatio. /// Grouping rules used in math realization.
static MATH_RULES: &[&GroupingRule] = &[&CITES, &LIST, &ENUM, &TERMS]; static MATH_RULES: &[&GroupingRule] = &[&CITES, &LIST, &ENUM, &TERMS];
/// Groups adjacent textual elements for text show rule application. /// Groups adjacent textual elements for text show rule application.
@ -801,6 +810,9 @@ static TEXTUAL: GroupingRule = GroupingRule {
tags: true, tags: true,
trigger: |content, _| { trigger: |content, _| {
let elem = content.elem(); let elem = content.elem();
// Note that `SymbolElem` converts into `TextElem` before textual show
// rules run, and we apply textual rules to elements manually during
// math realization, so we don't check for it here.
elem == TextElem::elem() elem == TextElem::elem()
|| elem == LinebreakElem::elem() || elem == LinebreakElem::elem()
|| elem == SmartQuoteElem::elem() || elem == SmartQuoteElem::elem()
@ -1124,7 +1136,16 @@ fn visit_regex_match<'a>(
m: RegexMatch<'a>, m: RegexMatch<'a>,
) -> SourceResult<()> { ) -> SourceResult<()> {
let match_range = m.offset..m.offset + m.text.len(); let match_range = m.offset..m.offset + m.text.len();
let piece = TextElem::packed(m.text);
// Replace with the correct intuitive element kind: if matching against a
// lone symbol, return a `SymbolElem`, otherwise return a newly composed
// `TextElem`. We should only match against a `SymbolElem` during math
// realization (`RealizationKind::Math`).
let piece = match elems {
&[(lone, _)] if lone.is::<SymbolElem>() => lone.clone(),
_ => TextElem::packed(m.text),
};
let context = Context::new(None, Some(m.styles)); let context = Context::new(None, Some(m.styles));
let output = m.recipe.apply(s.engine, context.track(), piece)?; let output = m.recipe.apply(s.engine, context.track(), piece)?;
@ -1147,10 +1168,16 @@ fn visit_regex_match<'a>(
continue; continue;
} }
// At this point, we can have a `TextElem`, `SpaceElem`, // At this point, we can have a `TextElem`, `SymbolElem`, `SpaceElem`,
// `LinebreakElem`, or `SmartQuoteElem`. We now determine the range of // `LinebreakElem`, or `SmartQuoteElem`. We now determine the range of
// the element. // the element.
let len = content.to_packed::<TextElem>().map_or(1, |elem| elem.text.len()); let len = if let Some(elem) = content.to_packed::<TextElem>() {
elem.text.len()
} else if let Some(elem) = content.to_packed::<SymbolElem>() {
elem.text.len_utf8()
} else {
1 // The rest are Ascii, so just one byte.
};
let elem_range = cursor..cursor + len; let elem_range = cursor..cursor + len;
// If the element starts before the start of match, visit it fully or // If the element starts before the start of match, visit it fully or

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

View File

@ -6,6 +6,14 @@
#test(upper(memes), "ARE MEMES GREAT?") #test(upper(memes), "ARE MEMES GREAT?")
#test(upper("Ελλάδα"), "ΕΛΛΆΔΑ") #test(upper("Ελλάδα"), "ΕΛΛΆΔΑ")
--- cases-content-text ---
// Check that cases are applied to text nested in content
#lower(box("HI!"))
--- cases-content-symbol ---
// Check that cases are applied to symbols nested in content
#lower($H I !$.body)
--- upper-bad-type --- --- upper-bad-type ---
// Error: 8-9 expected string or content, found integer // Error: 8-9 expected string or content, found integer
#upper(1) #upper(1)