diff --git a/crates/typst-library/src/layout/par.rs b/crates/typst-library/src/layout/par.rs index 6b862a23f..fe86f62a5 100644 --- a/crates/typst-library/src/layout/par.rs +++ b/crates/typst-library/src/layout/par.rs @@ -598,7 +598,11 @@ fn collect<'a>( if SmartQuoteElem::enabled_in(styles) { let lang = TextElem::lang_in(styles); let region = TextElem::region_in(styles); - let quotes = Quotes::from_lang(lang, region); + let quotes = Quotes::from_lang( + lang, + region, + SmartQuoteElem::alternative_in(styles), + ); let peeked = iter.peek().and_then(|child| { let child = if let Some((child, _)) = child.to_styled() { child diff --git a/crates/typst-library/src/text/quotes.rs b/crates/typst-library/src/text/quotes.rs index d07980642..bc3d00a06 100644 --- a/crates/typst-library/src/text/quotes.rs +++ b/crates/typst-library/src/text/quotes.rs @@ -42,6 +42,19 @@ pub struct SmartQuoteElem { /// ``` #[default(true)] pub enabled: bool, + + /// Whether to use alternative quotes. + /// + /// Does nothing for languages that don't have alternative quotes. + /// + /// ```example + /// #set text(lang: "de") + /// #set smartquote(alternative: true) + /// + /// "Das ist in anderen Anführungszeichen." + /// ``` + #[default(false)] + pub alternative: bool, } /// State machine for smart quote substitution. @@ -135,17 +148,25 @@ impl<'s> Quotes<'s> { /// Norwegian. /// /// For unknown languages, the English quotes are used. - pub fn from_lang(lang: Lang, region: Option) -> Self { + pub fn from_lang(lang: Lang, region: Option, alternative: bool) -> Self { let region = region.as_ref().map(Region::as_str); + + let low_high = ("‚", "‘", "„", "“"); + let (single_open, single_close, double_open, double_close) = match lang.as_str() { - "de" if matches!(region, Some("CH" | "LI")) => ("‹", "›", "«", "»"), - "cs" | "da" | "de" | "et" | "is" | "lt" | "lv" | "sk" | "sl" => { - ("‚", "‘", "„", "“") - } + "de" if matches!(region, Some("CH" | "LI")) => match alternative { + false => ("‹", "›", "«", "»"), + true => low_high, + }, + "cs" | "da" | "de" | "sk" | "sl" if alternative => ("›", "‹", "»", "«"), + "cs" | "da" | "de" | "et" | "is" | "lt" | "lv" | "sk" | "sl" => low_high, + "fr" | "ru" if alternative => return Self::default(), "fr" => ("‹\u{00A0}", "\u{00A0}›", "«\u{00A0}", "\u{00A0}»"), + "fi" | "sv" if alternative => ("’", "’", "»", "»"), "bs" | "fi" | "sv" => ("’", "’", "”", "”"), "es" if matches!(region, Some("ES") | None) => ("“", "”", "«", "»"), "hu" | "pl" | "ro" => ("’", "’", "„", "”"), + "no" | "nb" | "nn" if alternative => low_high, "ru" | "no" | "nb" | "nn" | "ua" => ("’", "’", "«", "»"), _ if lang.dir() == Dir::RTL => ("’", "‘", "”", "“"), _ => return Self::default(),