From 7838da02ec8a9ffbdfa61ed3dfedb24557a0e49c Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski Date: Mon, 29 Jul 2024 00:25:03 -0500 Subject: [PATCH] Add SymbolElem to realization --- crates/typst-realize/src/lib.rs | 53 ++++++++++++++++++++++------- tests/ref/cases-content-symbol.png | Bin 0 -> 191 bytes tests/ref/cases-content-text.png | Bin 0 -> 184 bytes tests/suite/text/case.typ | 8 +++++ 4 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 tests/ref/cases-content-symbol.png create mode 100644 tests/ref/cases-content-text.png diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index 99db2ef1b..ff42c3e95 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -221,7 +221,7 @@ impl<'a, 'x, 'y, 'z, 's> Grouped<'a, 'x, 'y, 'z, 's> { /// Handles an arbitrary piece of content during realization. fn visit<'a>( s: &mut State<'a, '_, '_, '_>, - mut content: &'a Content, + content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { // Tags can always simply be pushed. @@ -230,12 +230,6 @@ fn visit<'a>( return Ok(()); } - if let Some(elem) = content.to_packed::() { - // 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 // to happen before show rules. if visit_math_rules(s, content, styles)? { @@ -247,7 +241,7 @@ fn visit<'a>( 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. if let Some(sequence) = content.to_packed::() { for elem in &sequence.children { @@ -301,7 +295,14 @@ fn visit_math_rules<'a>( // In normal realization, we apply regex show rules to consecutive // textual elements via `TEXTUAL` grouping. However, in math, this is // not desirable, so we just do it on a per-element basis. - if let Some(elem) = content.to_packed::() { + if let Some(elem) = content.to_packed::() { + 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::() { if let Some(m) = find_regex_match_in_str(&elem.text, styles) { visit_regex_match(s, &[(content, styles)], m)?; return Ok(true); @@ -314,6 +315,14 @@ fn visit_math_rules<'a>( visit(s, s.store(eq), styles)?; 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::() { + let text = TextElem::packed(elem.text).spanned(elem.span()); + visit(s, s.store(text), styles)?; + return Ok(true); + } } Ok(false) @@ -792,7 +801,7 @@ static HTML_DOCUMENT_RULES: &[&GroupingRule] = /// Grouping rules used in HTML fragment realization. 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]; /// Groups adjacent textual elements for text show rule application. @@ -801,6 +810,9 @@ static TEXTUAL: GroupingRule = GroupingRule { tags: true, trigger: |content, _| { 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 == LinebreakElem::elem() || elem == SmartQuoteElem::elem() @@ -1124,7 +1136,16 @@ fn visit_regex_match<'a>( m: RegexMatch<'a>, ) -> SourceResult<()> { 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::() => lone.clone(), + _ => TextElem::packed(m.text), + }; + let context = Context::new(None, Some(m.styles)); let output = m.recipe.apply(s.engine, context.track(), piece)?; @@ -1147,10 +1168,16 @@ fn visit_regex_match<'a>( 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 // the element. - let len = content.to_packed::().map_or(1, |elem| elem.text.len()); + let len = if let Some(elem) = content.to_packed::() { + elem.text.len() + } else if let Some(elem) = content.to_packed::() { + elem.text.len_utf8() + } else { + 1 // The rest are Ascii, so just one byte. + }; let elem_range = cursor..cursor + len; // If the element starts before the start of match, visit it fully or diff --git a/tests/ref/cases-content-symbol.png b/tests/ref/cases-content-symbol.png new file mode 100644 index 0000000000000000000000000000000000000000..b0b8a65e322ce257658328c02455efa3f21dcdc7 GIT binary patch literal 191 zcmeAS@N?(olHy`uVBq!ia0vp^6+kS_0VEhE<%|3RQf;0tjv*Ddl7HAcG$dYm6xi*q zE9WN`Z)@Wqme138AzrP)-EMf%64NzaW>$N&Gof5Qv4 m3E>~}w>**BS^=@pjg{dlLr2oy8~h-LF?hQAxvX;IkOyX9M(PwfByU;F>^%|{*g7KX+Po-~LBj{NKa