From efde5cac88078f10485f715be66a27efba2f23d8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 24 Feb 2022 19:56:01 +0100 Subject: [PATCH] Lower and upper on templates --- src/eval/template.rs | 2 +- src/library/text.rs | 33 ++++++++++++++++++++++++++++++--- src/library/utility.rs | 15 +++++++++++++-- tests/typ/utility/strings.typ | 7 ++++++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/eval/template.rs b/src/eval/template.rs index fb6246002..747e5d442 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -114,8 +114,8 @@ impl Template { if let Some((_, map)) = Arc::get_mut(styled) { if !map.has_scoped() { map.set(key, value); + return self; } - return self; } } diff --git a/src/library/text.rs b/src/library/text.rs index ba7f4c200..448ba9af0 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -95,6 +95,9 @@ impl TextNode { /// Whether a monospace font should be preferred. #[skip] pub const MONOSPACED: bool = false; + /// The case transformation that should be applied to the next. + #[skip] + pub const CASE: Option = None; /// Decorative lines. #[skip] #[fold(|a, b| a.into_iter().chain(b).collect())] @@ -384,6 +387,25 @@ castable! { .collect(), } +/// A case transformation on text. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Case { + /// Everything is uppercased. + Upper, + /// Everything is lowercased. + Lower, +} + +impl Case { + /// Apply the case to a string of text. + pub fn apply(self, text: &str) -> String { + match self { + Self::Upper => text.to_uppercase(), + Self::Lower => text.to_lowercase(), + } + } +} + /// Shape text into [`ShapedText`]. pub fn shape<'a>( fonts: &mut FontStore, @@ -391,13 +413,18 @@ pub fn shape<'a>( styles: StyleChain<'a>, dir: Dir, ) -> ShapedText<'a> { + let text = match styles.get(TextNode::CASE) { + Some(case) => Cow::Owned(case.apply(text)), + None => Cow::Borrowed(text), + }; + let mut glyphs = vec![]; if !text.is_empty() { shape_segment( fonts, &mut glyphs, 0, - text, + &text, variant(styles), families(styles), None, @@ -743,7 +770,7 @@ fn tags(styles: StyleChain) -> Vec { #[derive(Debug, Clone)] pub struct ShapedText<'a> { /// The text that was shaped. - pub text: &'a str, + pub text: Cow<'a, str>, /// The text direction. pub dir: Dir, /// The text's style properties. @@ -941,7 +968,7 @@ impl<'a> ShapedText<'a> { if let Some(glyphs) = self.slice_safe_to_break(text_range.clone()) { let (size, baseline) = measure(fonts, glyphs, self.styles); Self { - text: &self.text[text_range], + text: Cow::Borrowed(&self.text[text_range]), dir: self.dir, styles: self.styles.clone(), size, diff --git a/src/library/utility.rs b/src/library/utility.rs index 051dd8854..ceae20bfc 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -4,6 +4,7 @@ use std::cmp::Ordering; use std::str::FromStr; use super::prelude::*; +use super::{Case, TextNode}; use crate::eval::Array; /// Ensure that a condition is fulfilled. @@ -253,12 +254,22 @@ pub fn range(_: &mut Context, args: &mut Args) -> TypResult { /// Convert a string to lowercase. pub fn lower(_: &mut Context, args: &mut Args) -> TypResult { - Ok(args.expect::("string")?.to_lowercase().into()) + case(Case::Lower, args) } /// Convert a string to uppercase. pub fn upper(_: &mut Context, args: &mut Args) -> TypResult { - Ok(args.expect::("string")?.to_uppercase().into()) + case(Case::Upper, args) +} + +/// Change the case of a string or template. +fn case(case: Case, args: &mut Args) -> TypResult { + let Spanned { v, span } = args.expect("string or template")?; + Ok(match v { + Value::Str(v) => Value::Str(case.apply(&v).into()), + Value::Template(v) => Value::Template(v.styled(TextNode::CASE, Some(case))), + v => bail!(span, "expected string or template, found {}", v.type_name()), + }) } /// The length of a string, an array or a dictionary. diff --git a/tests/typ/utility/strings.typ b/tests/typ/utility/strings.typ index 87be630f0..91be0faa3 100644 --- a/tests/typ/utility/strings.typ +++ b/tests/typ/utility/strings.typ @@ -2,7 +2,8 @@ --- // Test the `upper`, `lower`, and number formatting functions. -#upper("Abc 8 def") +#upper("Abc 8") +#upper[def] #lower("SCREAMING MUST BE SILENCED in " + roman(1672) + " years") @@ -14,6 +15,10 @@ parbreak() } +--- +// Error: 8-9 expected string or template, found integer +#upper(1) + --- // Error: 9-11 must be at least zero #symbol(-1)