From aabb4b5ecf67a5b51b0e550f0b06087465f4ba75 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 15 Feb 2024 10:41:27 +0100 Subject: [PATCH] Better quote selection (#3422) --- crates/typst/src/foundations/styles.rs | 10 ++++++++ crates/typst/src/layout/inline/mod.rs | 13 ++++------ crates/typst/src/model/list.rs | 11 +------- crates/typst/src/model/quote.rs | 34 ++++++++++++++++++++----- crates/typst/src/text/smartquote.rs | 8 +++--- tests/ref/text/quote-nesting.png | Bin 0 -> 14034 bytes tests/typ/text/quote-nesting.typ | 27 ++++++++++++++++++++ 7 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 tests/ref/text/quote-nesting.png create mode 100644 tests/typ/text/quote-nesting.typ diff --git a/crates/typst/src/foundations/styles.rs b/crates/typst/src/foundations/styles.rs index 9656fafbf..9472e207a 100644 --- a/crates/typst/src/foundations/styles.rs +++ b/crates/typst/src/foundations/styles.rs @@ -745,3 +745,13 @@ impl Fold for SmallVec<[T; N]> { self } } + +/// A type that accumulates depth when folded. +#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)] +pub struct Depth(pub usize); + +impl Fold for Depth { + fn fold(self, outer: Self) -> Self { + Self(outer.0 + self.0) + } +} diff --git a/crates/typst/src/layout/inline/mod.rs b/crates/typst/src/layout/inline/mod.rs index 3e3b7a76a..17407bfae 100644 --- a/crates/typst/src/layout/inline/mod.rs +++ b/crates/typst/src/layout/inline/mod.rs @@ -464,15 +464,12 @@ fn collect<'a>( Segment::Text(c.len_utf8()) } else if let Some(elem) = child.to_packed::() { let prev = full.len(); - if SmartQuoteElem::enabled_in(styles) { - let quotes = SmartQuoteElem::quotes_in(styles); - let lang = TextElem::lang_in(styles); - let region = TextElem::region_in(styles); + if elem.enabled(styles) { let quotes = SmartQuotes::new( - quotes, - lang, - region, - SmartQuoteElem::alternative_in(styles), + elem.quotes(styles), + TextElem::lang_in(styles), + TextElem::region_in(styles), + elem.alternative(styles), ); let peeked = iter.peek().and_then(|&child| { let child = if let Some(styled) = child.to_packed::() { diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs index ebd733e32..6de78bbd5 100644 --- a/crates/typst/src/model/list.rs +++ b/crates/typst/src/model/list.rs @@ -1,7 +1,7 @@ use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{ - cast, elem, scope, Array, Content, Fold, Func, Packed, Smart, StyleChain, Value, + cast, elem, scope, Array, Content, Depth, Func, Packed, Smart, StyleChain, Value, }; use crate::layout::{ Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, @@ -236,12 +236,3 @@ cast! { }, v: Func => Self::Func(v), } - -#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)] -struct Depth(usize); - -impl Fold for Depth { - fn fold(self, outer: Self) -> Self { - Self(outer.0 + self.0) - } -} diff --git a/crates/typst/src/model/quote.rs b/crates/typst/src/model/quote.rs index 883f876e9..d02208b6d 100644 --- a/crates/typst/src/model/quote.rs +++ b/crates/typst/src/model/quote.rs @@ -1,12 +1,12 @@ use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{ - cast, elem, Content, Label, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, - Styles, + cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart, + StyleChain, Styles, }; use crate::layout::{Alignment, BlockElem, Em, HElem, PadElem, Spacing, VElem}; use crate::model::{CitationForm, CiteElem}; -use crate::text::{SmartQuoteElem, SpaceElem, TextElem}; +use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem}; /// Displays a quote alongside an optional attribution. /// @@ -126,6 +126,12 @@ pub struct QuoteElem { /// The quote. #[required] body: Content, + + /// The nesting depth. + #[internal] + #[fold] + #[ghost] + depth: Depth, } /// Attribution for a [quote](QuoteElem). @@ -152,11 +158,27 @@ impl Show for Packed { let block = self.block(styles); if self.quotes(styles) == Smart::Custom(true) || !block { + let quotes = SmartQuotes::new( + SmartQuoteElem::quotes_in(styles), + TextElem::lang_in(styles), + TextElem::region_in(styles), + SmartQuoteElem::alternative_in(styles), + ); + + // Alternate between single and double quotes. + let Depth(depth) = QuoteElem::depth_in(styles); + let double = depth % 2 == 0; + // Add zero-width weak spacing to make the quotes "sticky". let hole = HElem::hole().pack(); - let quote = SmartQuoteElem::new().with_double(true).pack(); - realized = - Content::sequence([quote.clone(), hole.clone(), realized, hole, quote]); + realized = Content::sequence([ + TextElem::packed(quotes.open(double)), + hole.clone(), + realized, + hole, + TextElem::packed(quotes.close(double)), + ]) + .styled(QuoteElem::set_depth(Depth(1))); } if block { diff --git a/crates/typst/src/text/smartquote.rs b/crates/typst/src/text/smartquote.rs index 9e62bea35..0c435f112 100644 --- a/crates/typst/src/text/smartquote.rs +++ b/crates/typst/src/text/smartquote.rs @@ -251,7 +251,7 @@ impl<'s> SmartQuotes<'s> { } /// The opening quote. - fn open(&self, double: bool) -> &'s str { + pub fn open(&self, double: bool) -> &'s str { if double { self.double_open } else { @@ -260,7 +260,7 @@ impl<'s> SmartQuotes<'s> { } /// The closing quote. - fn close(&self, double: bool) -> &'s str { + pub fn close(&self, double: bool) -> &'s str { if double { self.double_close } else { @@ -269,7 +269,7 @@ impl<'s> SmartQuotes<'s> { } /// Which character should be used as a prime. - fn prime(&self, double: bool) -> &'static str { + pub fn prime(&self, double: bool) -> &'static str { if double { "″" } else { @@ -278,7 +278,7 @@ impl<'s> SmartQuotes<'s> { } /// Which character should be used as a fallback quote. - fn fallback(&self, double: bool) -> &'static str { + pub fn fallback(&self, double: bool) -> &'static str { if double { "\"" } else { diff --git a/tests/ref/text/quote-nesting.png b/tests/ref/text/quote-nesting.png new file mode 100644 index 0000000000000000000000000000000000000000..fb16002de9315e8aabb96632823162a92d4cb74d GIT binary patch literal 14034 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 literal 0 HcmV?d00001 diff --git a/tests/typ/text/quote-nesting.typ b/tests/typ/text/quote-nesting.typ new file mode 100644 index 000000000..381aaa569 --- /dev/null +++ b/tests/typ/text/quote-nesting.typ @@ -0,0 +1,27 @@ +// Test quote nesting. + +--- +// Test quote selection. +#set page(width: auto) +#set text(lang: "en") +=== EN +#quote[An apostroph'] \ +#quote[A #quote[nested] quote] \ +#quote[A #quote[very #quote[nested]] quote] + +#set text(lang: "de") +=== DE +#quote[Satz mit Apostroph'] \ +#quote[Satz mit #quote[Zitat]] \ +#quote[A #quote[very #quote[nested]] quote] + +#set smartquote(alternative: true) +=== DE Alternative +#quote[Satz mit Apostroph'] \ +#quote[Satz mit #quote[Zitat]] \ +#quote[A #quote[very #quote[nested]] quote] + +--- +// With custom quotes. +#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")"))) +#quote[A #quote[nested] quote]