diff --git a/crates/typst-layout/src/math/mod.rs b/crates/typst-layout/src/math/mod.rs index 06dc6653b..905e159ab 100644 --- a/crates/typst-layout/src/math/mod.rs +++ b/crates/typst-layout/src/math/mod.rs @@ -17,7 +17,9 @@ use rustybuzz::Feature; use ttf_parser::Tag; use typst_library::diag::{bail, SourceResult}; use typst_library::engine::Engine; -use typst_library::foundations::{Content, NativeElement, Packed, Resolve, StyleChain}; +use typst_library::foundations::{ + Content, NativeElement, Packed, Resolve, StyleChain, SymbolElem, +}; use typst_library::introspection::{Counter, Locator, SplitLocator, TagElem}; use typst_library::layout::{ Abs, AlignElem, Axes, BlockElem, BoxElem, Em, FixedAlignment, Fragment, Frame, HElem, @@ -535,6 +537,12 @@ fn layout_realized( layout_h(elem, ctx, styles)?; } else if let Some(elem) = elem.to_packed::() { self::text::layout_text(elem, ctx, styles)?; + } else if let Some(elem) = elem.to_packed::() { + // This is a hack to avoid affecting layout that will be replaced in a + // later commit. + let text_elem = TextElem::new(elem.text.to_string().into()); + let packed = Packed::new(text_elem); + self::text::layout_text(&packed, ctx, styles)?; } else if let Some(elem) = elem.to_packed::() { layout_box(elem, ctx, styles)?; } else if elem.is::() { diff --git a/crates/typst-library/src/foundations/ops.rs b/crates/typst-library/src/foundations/ops.rs index 85a041b6c..7dbdde8ff 100644 --- a/crates/typst-library/src/foundations/ops.rs +++ b/crates/typst-library/src/foundations/ops.rs @@ -6,7 +6,9 @@ use ecow::eco_format; use typst_utils::Numeric; use crate::diag::{bail, HintedStrResult, StrResult}; -use crate::foundations::{format_str, Datetime, IntoValue, Regex, Repr, Value}; +use crate::foundations::{ + format_str, Datetime, IntoValue, Regex, Repr, SymbolElem, Value, +}; use crate::layout::{Alignment, Length, Rel}; use crate::text::TextElem; use crate::visualize::Stroke; @@ -30,10 +32,10 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult { (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), (Bytes(a), Bytes(b)) => Bytes(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())), + (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())), (Content(a), Str(b)) => Content(a + TextElem::packed(b)), (Str(a), Content(b)) => Content(TextElem::packed(a) + b), - (Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b), + (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), (Args(a), Args(b)) => Args(a + b), @@ -130,10 +132,10 @@ pub fn add(lhs: Value, rhs: Value) -> HintedStrResult { (Symbol(a), Str(b)) => Str(format_str!("{a}{b}")), (Bytes(a), Bytes(b)) => Bytes(a + b), (Content(a), Content(b)) => Content(a + b), - (Content(a), Symbol(b)) => Content(a + TextElem::packed(b.get())), + (Content(a), Symbol(b)) => Content(a + SymbolElem::packed(b.get())), (Content(a), Str(b)) => Content(a + TextElem::packed(b)), (Str(a), Content(b)) => Content(TextElem::packed(a) + b), - (Symbol(a), Content(b)) => Content(TextElem::packed(a.get()) + b), + (Symbol(a), Content(b)) => Content(SymbolElem::packed(a.get()) + b), (Array(a), Array(b)) => Array(a + b), (Dict(a), Dict(b)) => Dict(a + b), diff --git a/crates/typst-library/src/foundations/symbol.rs b/crates/typst-library/src/foundations/symbol.rs index 3045970de..8a80506fe 100644 --- a/crates/typst-library/src/foundations/symbol.rs +++ b/crates/typst-library/src/foundations/symbol.rs @@ -9,7 +9,10 @@ use typst_syntax::{is_ident, Span, Spanned}; use typst_utils::hash128; use crate::diag::{bail, SourceResult, StrResult}; -use crate::foundations::{cast, func, scope, ty, Array, Func, NativeFunc, Repr as _}; +use crate::foundations::{ + cast, elem, func, scope, ty, Array, Content, Func, NativeElement, NativeFunc, Packed, + PlainText, Repr as _, +}; /// A Unicode symbol. /// @@ -425,3 +428,31 @@ fn parts(modifiers: &str) -> impl Iterator { fn contained(modifiers: &str, m: &str) -> bool { parts(modifiers).any(|part| part == m) } + +/// A single character. +#[elem(Repr, PlainText)] +pub struct SymbolElem { + /// The symbol's character. + #[required] + pub text: char, // This is called `text` for consistency with `TextElem`. +} + +impl SymbolElem { + /// Create a new packed symbol element. + pub fn packed(text: impl Into) -> Content { + Self::new(text.into()).pack() + } +} + +impl PlainText for Packed { + fn plain_text(&self, text: &mut EcoString) { + text.push(self.text); + } +} + +impl crate::foundations::Repr for SymbolElem { + /// Use a custom repr that matches normal content. + fn repr(&self) -> EcoString { + eco_format!("[{}]", self.text) + } +} diff --git a/crates/typst-library/src/foundations/value.rs b/crates/typst-library/src/foundations/value.rs index efc480d3f..8d9f59332 100644 --- a/crates/typst-library/src/foundations/value.rs +++ b/crates/typst-library/src/foundations/value.rs @@ -16,7 +16,7 @@ use crate::foundations::{ fields, ops, repr, Args, Array, AutoValue, Bytes, CastInfo, Content, Datetime, Decimal, Dict, Duration, Fold, FromValue, Func, IntoValue, Label, Module, NativeElement, NativeType, NoneValue, Plugin, Reflect, Repr, Resolve, Scope, Str, - Styles, Symbol, Type, Version, + Styles, Symbol, SymbolElem, Type, Version, }; use crate::layout::{Abs, Angle, Em, Fr, Length, Ratio, Rel}; use crate::text::{RawContent, RawElem, TextElem}; @@ -209,7 +209,7 @@ impl Value { Self::Decimal(v) => TextElem::packed(eco_format!("{v}")), Self::Str(v) => TextElem::packed(v), Self::Version(v) => TextElem::packed(eco_format!("{v}")), - Self::Symbol(v) => TextElem::packed(v.get()), + Self::Symbol(v) => SymbolElem::packed(v.get()), Self::Content(v) => v, Self::Module(module) => module.content(), _ => RawElem::new(RawContent::Text(self.repr())) @@ -656,7 +656,7 @@ primitive! { Duration: "duration", Duration } primitive! { Content: "content", Content, None => Content::empty(), - Symbol(v) => TextElem::packed(v.get()), + Symbol(v) => SymbolElem::packed(v.get()), Str(v) => TextElem::packed(v) } primitive! { Styles: "styles", Styles } diff --git a/crates/typst-library/src/math/accent.rs b/crates/typst-library/src/math/accent.rs index b87e527f2..b162c52b1 100644 --- a/crates/typst-library/src/math/accent.rs +++ b/crates/typst-library/src/math/accent.rs @@ -1,8 +1,7 @@ use crate::diag::bail; -use crate::foundations::{cast, elem, func, Content, NativeElement, Value}; +use crate::foundations::{cast, elem, func, Content, NativeElement, SymbolElem}; use crate::layout::{Length, Rel}; use crate::math::Mathy; -use crate::text::TextElem; /// Attaches an accent to a base. /// @@ -142,8 +141,8 @@ cast! { Accent, self => self.0.into_value(), v: char => Self::new(v), - v: Content => match v.to_packed::() { - Some(elem) => Value::Str(elem.text.clone().into()).cast()?, - None => bail!("expected text"), + v: Content => match v.to_packed::() { + Some(elem) => Self::new(elem.text), + None => bail!("expected a symbol"), }, } diff --git a/crates/typst-realize/src/lib.rs b/crates/typst-realize/src/lib.rs index 6ab6d81c5..99db2ef1b 100644 --- a/crates/typst-realize/src/lib.rs +++ b/crates/typst-realize/src/lib.rs @@ -16,7 +16,7 @@ use typst_library::engine::Engine; use typst_library::foundations::{ Content, Context, ContextElem, Element, NativeElement, Recipe, RecipeIndex, Selector, SequenceElem, Show, ShowSet, Style, StyleChain, StyleVec, StyledElem, Styles, - Synthesize, Transformation, + SymbolElem, Synthesize, Transformation, }; use typst_library::html::{tag, HtmlElem}; use typst_library::introspection::{Locatable, SplitLocator, Tag, TagElem}; @@ -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, '_, '_, '_>, - content: &'a Content, + mut content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<()> { // Tags can always simply be pushed. @@ -230,6 +230,12 @@ 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)? { diff --git a/tests/suite/foundations/content.typ b/tests/suite/foundations/content.typ index 31ef1c54c..9ddee5975 100644 --- a/tests/suite/foundations/content.typ +++ b/tests/suite/foundations/content.typ @@ -50,12 +50,14 @@ `raw` --- content-fields-complex --- -// Integrated test for content fields. +// Integrated test for content fields. The idea is to parse a normal looking +// equation and symbolically evaluate it with the given variable values. + #let compute(equation, ..vars) = { let vars = vars.named() let f(elem) = { let func = elem.func() - if func == text { + if elem.has("text") { let text = elem.text if regex("^\d+$") in text { int(text) @@ -74,7 +76,7 @@ elem .children .filter(v => v != [ ]) - .split[+] + .split($+$.body) .map(xs => xs.fold(1, (prod, v) => prod * f(v))) .fold(0, (sum, v) => sum + v) } @@ -83,13 +85,15 @@ [With ] vars .pairs() - .map(p => $#p.first() = #p.last()$) + .map(((name, value)) => $name = value$) .join(", ", last: " and ") [ we have:] $ equation = result $ } #compute($x y + y^2$, x: 2, y: 3) +// This should generate the same output as: +// With $x = 2$ and $y = 3$ we have: $ x y + y^2 = 15 $ --- content-label-has-method --- // Test whether the label is accessible through the `has` method. diff --git a/tests/suite/math/symbols.typ b/tests/suite/math/symbols.typ index 65a483162..6dd9db622 100644 --- a/tests/suite/math/symbols.typ +++ b/tests/suite/math/symbols.typ @@ -2,7 +2,7 @@ --- math-symbol-basic --- #let sym = symbol("s", ("basic", "s")) -#test($sym.basic$, $#"s"$) +#test($sym.basic$, $s$) --- math-symbol-underscore --- #let sym = symbol("s", ("test_underscore", "s")) @@ -16,7 +16,7 @@ $sym.test-dash$ --- math-symbol-double --- #let sym = symbol("s", ("test.basic", "s")) -#test($sym.test.basic$, $#"s"$) +#test($sym.test.basic$, $s$) --- math-symbol-double-underscore --- #let sym = symbol("s", ("one.test_underscore", "s"))