From fcdccc9cbae02be0ac61f5f50f7f1a256fdd2b11 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 2 Oct 2024 13:51:09 +0200 Subject: [PATCH] Fix textual grouping (#5097) --- crates/typst/src/realize.rs | 49 ++++++++++++++-------------- tests/ref/show-text-in-citation.png | Bin 0 -> 795 bytes tests/suite/styling/show-text.typ | 12 +++++++ 3 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 tests/ref/show-text-in-citation.png diff --git a/crates/typst/src/realize.rs b/crates/typst/src/realize.rs index f999bfd42..7c199ff16 100644 --- a/crates/typst/src/realize.rs +++ b/crates/typst/src/realize.rs @@ -873,40 +873,41 @@ const fn list_like_grouping() -> GroupingRule { /// as part of a paragraph grouping, /// - if that's not possible because another grouping is active, temporarily /// disables textual grouping and revisits the elements. -fn finish_textual(Grouped { s, start }: Grouped) -> SourceResult<()> { - // Try to find a regex match in the grouped textual elements. +fn finish_textual(Grouped { s, mut start }: Grouped) -> SourceResult<()> { + // Try to find a regex match in the grouped textual elements. Returns early + // if there is one. if visit_textual(s, start)? { return Ok(()); } - // No regex match. - match s.groupings.last() { - // Transparently apply the grouped content to an active paragraph. This - // is more efficient than revisiting everything. Checking the priority - // is a bit of a hack, but the simplest way to check which rule is - // active for now. - Some(grouping) if std::ptr::eq(grouping.rule, &PAR) => {} + // There was no regex match, so we need to collect the text into a paragraph + // grouping. To do that, we first terminate all non-paragraph groupings. + if in_non_par_grouping(s) { + let elems = s.store_slice(&s.sink[start..]); + s.sink.truncate(start); + finish_grouping_while(s, in_non_par_grouping)?; + start = s.sink.len(); + s.sink.extend(elems); + } - // Start a new paragraph based on this textual group. - None => s.groupings.push(Grouping { rule: &PAR, start }), - - // If a non-paragraph grouping is top-level, revisit the grouped - // content with the `TEXTUAL` rule disabled. - _ => { - let elems = s.store_slice(&s.sink[start..]); - let rules = s.rules; - s.sink.truncate(start); - s.rules = &s.rules[1..]; - for &(content, styles) in &elems { - visit(s, content, styles)?; - } - s.rules = rules; - } + // Now, there are only two options: + // 1. We are already in a paragraph group. In this case, the elements just + // transparently become part of it. + // 2. There is no group at all. In this case, we create one. + if s.groupings.is_empty() { + s.groupings.push(Grouping { start, rule: &PAR }); } Ok(()) } +/// Whether there is an active grouping, but it is not a `PAR` grouping. +fn in_non_par_grouping(s: &State) -> bool { + s.groupings + .last() + .is_some_and(|grouping| !std::ptr::eq(grouping.rule, &PAR)) +} + /// Builds the `ParElem` from inline-level elements. fn finish_par(mut grouped: Grouped) -> SourceResult<()> { // Collapse unsupported spaces in-place. diff --git a/tests/ref/show-text-in-citation.png b/tests/ref/show-text-in-citation.png new file mode 100644 index 0000000000000000000000000000000000000000..392487bc8a53191a74ffe07174bfa3f61f27e2ae GIT binary patch literal 795 zcmV+$1LXXPP)CT(r1SIhjg5_zl$0bSBv@EjczAgC_xB(m zAbx&+ZEbDm=jYeg*Eu;kCnqNg(_5>Fp~mH6bA(Y;0`t@$rz5klx?p$;!?-KnnKrmNkfsNJfr-J+-6p{3oQqurjOuCTc8@AImwuc@i2zQD-c zw!G%JyWNI~-FJE2!^z!_lIDAQXJ~Aemz)2;yWP{<-PGIL+uPpW-tO)2-CSMY-sS(@ z+yCF*^Yixi_xXB!gr%pi-OSV6-sb=7>Lw^K+T7&-@$fl2L*2y7|NHyj;OE`n=Tb?V z9{>OWHAzH4RCwC$)x~lGQ4odUGcbg>ySux)ySux)yF!4#oTpArg$KwY)r-{q7XP+) zr=ci{@+a=iSOXx8yNBcljdZ65Sdvpx(;&mSMEARkMsbM-AjxLUhK%Yel3z5~+yp=m z$jE_=no9rSu1)}YU}$d-MAX*#4|jI}&~51Jhlt90((R(bc@02I8)AYXqOn2Kt}gfY z78l+COKW?O9YXAJFdc+ITD%$Som$d=?9+_T17SS#(ot4w}--;T{k!}4L}c$%*}&{lMV7{jRq&Dex5{D zAw+DS({awx5zcA=!b6Z%1Q9Dk!^amF<6|>(uYe^YG$<271_xle_CVUR8Q+UqQbkdI Z