diff options
| author | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2023-03-07 15:17:13 +0100 |
| commit | 25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch) | |
| tree | 2fbb4650903123da047a1f1f11a0abda95286e12 /library/src/text | |
| parent | 6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff) | |
Fully untyped model
Diffstat (limited to 'library/src/text')
| -rw-r--r-- | library/src/text/deco.rs | 180 | ||||
| -rw-r--r-- | library/src/text/misc.rs | 209 | ||||
| -rw-r--r-- | library/src/text/mod.rs | 373 | ||||
| -rw-r--r-- | library/src/text/quotes.rs | 35 | ||||
| -rw-r--r-- | library/src/text/raw.rs | 163 | ||||
| -rw-r--r-- | library/src/text/shaping.rs | 15 | ||||
| -rw-r--r-- | library/src/text/shift.rs | 100 |
7 files changed, 544 insertions, 531 deletions
diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 4043141b..18145d28 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -4,7 +4,6 @@ use ttf_parser::{GlyphId, OutlineBuilder}; use super::TextNode; use crate::prelude::*; -/// # Underline /// Underline text. /// /// ## Example @@ -12,19 +11,15 @@ use crate::prelude::*; /// This is #underline[important]. /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The content to underline. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct UnderlineNode(pub Content); - -#[node] -impl UnderlineNode { +/// Display: Underline +/// Category: text +#[node(Show)] +pub struct UnderlineNode { + /// The content to underline. + #[positional] + #[required] + pub body: Content, + /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -35,8 +30,12 @@ impl UnderlineNode { /// [care], /// ) /// ``` - #[property(shorthand, resolve, fold)] - pub const STROKE: Smart<PartialStroke> = Smart::Auto; + #[settable] + #[shorthand] + #[resolve] + #[fold] + #[default] + pub stroke: Smart<PartialStroke>, /// Position of the line relative to the baseline, read from the font tables /// if `{auto}`. @@ -46,8 +45,10 @@ impl UnderlineNode { /// The Tale Of A Faraway Line I /// ] /// ``` - #[property(resolve)] - pub const OFFSET: Smart<Length> = Smart::Auto; + #[settable] + #[resolve] + #[default] + pub offset: Smart<Length>, /// Amount that the line will be longer or shorter than its associated text. /// @@ -56,8 +57,10 @@ impl UnderlineNode { /// underline(extent: 2pt)[Chapter 1] /// ) /// ``` - #[property(resolve)] - pub const EXTENT: Length = Length::zero(); + #[settable] + #[resolve] + #[default] + pub extent: Length, /// Whether the line skips sections in which it would collide with the /// glyphs. @@ -66,23 +69,14 @@ impl UnderlineNode { /// This #underline(evade: true)[is great]. /// This #underline(evade: false)[is less great]. /// ``` - pub const EVADE: bool = true; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } + #[settable] + #[default(true)] + pub evade: bool, } impl Show for UnderlineNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { - Ok(self.0.clone().styled( + Ok(self.body().styled( TextNode::DECO, Decoration { line: DecoLine::Underline, @@ -95,7 +89,6 @@ impl Show for UnderlineNode { } } -/// # Overline /// Add a line over text. /// /// ## Example @@ -103,19 +96,15 @@ impl Show for UnderlineNode { /// #overline[A line over text.] /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The content to add a line over. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct OverlineNode(pub Content); - -#[node] -impl OverlineNode { +/// Display: Overline +/// Category: text +#[node(Show)] +pub struct OverlineNode { + /// The content to add a line over. + #[positional] + #[required] + pub body: Content, + /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -127,8 +116,12 @@ impl OverlineNode { /// [The Forest Theme], /// ) /// ``` - #[property(shorthand, resolve, fold)] - pub const STROKE: Smart<PartialStroke> = Smart::Auto; + #[settable] + #[shorthand] + #[resolve] + #[fold] + #[default] + pub stroke: Smart<PartialStroke>, /// Position of the line relative to the baseline, read from the font tables /// if `{auto}`. @@ -138,8 +131,10 @@ impl OverlineNode { /// The Tale Of A Faraway Line II /// ] /// ``` - #[property(resolve)] - pub const OFFSET: Smart<Length> = Smart::Auto; + #[settable] + #[resolve] + #[default] + pub offset: Smart<Length>, /// Amount that the line will be longer or shorter than its associated text. /// @@ -148,8 +143,10 @@ impl OverlineNode { /// #set underline(extent: 4pt) /// #overline(underline[Typography Today]) /// ``` - #[property(resolve)] - pub const EXTENT: Length = Length::zero(); + #[settable] + #[resolve] + #[default] + pub extent: Length, /// Whether the line skips sections in which it would collide with the /// glyphs. @@ -163,23 +160,14 @@ impl OverlineNode { /// [Temple], /// ) /// ``` - pub const EVADE: bool = true; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } + #[settable] + #[default(true)] + pub evade: bool, } impl Show for OverlineNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { - Ok(self.0.clone().styled( + Ok(self.body().styled( TextNode::DECO, Decoration { line: DecoLine::Overline, @@ -192,7 +180,6 @@ impl Show for OverlineNode { } } -/// # Strikethrough /// Strike through text. /// /// ## Example @@ -200,19 +187,15 @@ impl Show for OverlineNode { /// This is #strike[not] relevant. /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The content to strike through. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct StrikeNode(pub Content); - -#[node] -impl StrikeNode { +/// Display: Strikethrough +/// Category: text +#[node(Show)] +pub struct StrikeNode { + /// The content to strike through. + #[positional] + #[required] + pub body: Content, + /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// @@ -223,8 +206,12 @@ impl StrikeNode { /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ /// This is #strike(stroke: 10pt)[redacted]. /// ``` - #[property(shorthand, resolve, fold)] - pub const STROKE: Smart<PartialStroke> = Smart::Auto; + #[settable] + #[shorthand] + #[resolve] + #[fold] + #[default] + pub stroke: Smart<PartialStroke>, /// Position of the line relative to the baseline, read from the font tables /// if `{auto}`. @@ -236,8 +223,10 @@ impl StrikeNode { /// This is #strike(offset: auto)[low-ish]. \ /// This is #strike(offset: -3.5pt)[on-top]. /// ``` - #[property(resolve)] - pub const OFFSET: Smart<Length> = Smart::Auto; + #[settable] + #[resolve] + #[default] + pub offset: Smart<Length>, /// Amount that the line will be longer or shorter than its associated text. /// @@ -245,24 +234,15 @@ impl StrikeNode { /// This #strike(extent: -2pt)[skips] parts of the word. /// This #strike(extent: 2pt)[extends] beyond the word. /// ``` - #[property(resolve)] - pub const EXTENT: Length = Length::zero(); - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } + #[settable] + #[resolve] + #[default] + pub extent: Length, } impl Show for StrikeNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { - Ok(self.0.clone().styled( + Ok(self.body().styled( TextNode::DECO, Decoration { line: DecoLine::Strikethrough, @@ -294,6 +274,10 @@ impl Fold for Decoration { } } +cast_from_value! { + Decoration: "decoration", +} + /// A kind of decorative line. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum DecoLine { diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index de7974cd..9caaf68e 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -1,24 +1,12 @@ use super::TextNode; use crate::prelude::*; -/// # Space /// A text space. /// -/// ## Category -/// text -#[func] -#[capable(Unlabellable, Behave)] -#[derive(Debug, Hash)] -pub struct SpaceNode; - -#[node] -impl SpaceNode { - fn construct(_: &Vm, _: &mut Args) -> SourceResult<Content> { - Ok(Self.pack()) - } -} - -impl Unlabellable for SpaceNode {} +/// Display: Space +/// Category: text +#[node(Unlabellable, Behave)] +pub struct SpaceNode {} impl Behave for SpaceNode { fn behaviour(&self) -> Behaviour { @@ -26,7 +14,8 @@ impl Behave for SpaceNode { } } -/// # Line Break +impl Unlabellable for SpaceNode {} + /// Inserts a line break. /// /// Advances the paragraph to the next line. A single trailing line break at the @@ -45,46 +34,34 @@ impl Behave for SpaceNode { /// a backslash followed by whitespace. This always creates an unjustified /// break. /// -/// ## Parameters -/// - justify: `bool` (named) -/// Whether to justify the line before the break. -/// -/// This is useful if you found a better line break opportunity in your -/// justified text than Typst did. -/// -/// ```example -/// #set par(justify: true) -/// #let jb = linebreak(justify: true) -/// -/// I have manually tuned the #jb -/// line breaks in this paragraph #jb -/// for an _interesting_ result. #jb -/// ``` -/// -/// ## Category -/// text -#[func] -#[capable(Behave)] -#[derive(Debug, Hash)] +/// Display: Line Break +/// Category: text +#[node(Behave)] pub struct LinebreakNode { + /// Whether to justify the line before the break. + /// + /// This is useful if you found a better line break opportunity in your + /// justified text than Typst did. + /// + /// ```example + /// #set par(justify: true) + /// #let jb = linebreak(justify: true) + /// + /// I have manually tuned the #jb + /// line breaks in this paragraph #jb + /// for an _interesting_ result. #jb + /// ``` + #[named] + #[default(false)] pub justify: bool, } -#[node] -impl LinebreakNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - let justify = args.named("justify")?.unwrap_or(false); - Ok(Self { justify }.pack()) - } -} - impl Behave for LinebreakNode { fn behaviour(&self) -> Behaviour { Behaviour::Destructive } } -/// # Strong Emphasis /// Strongly emphasizes content by increasing the font weight. /// /// Increases the current font weight by a given `delta`. @@ -104,42 +81,29 @@ impl Behave for LinebreakNode { /// word boundaries. To strongly emphasize part of a word, you have to use the /// function. /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The content to strongly emphasize. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct StrongNode(pub Content); +/// Display: Strong Emphasis +/// Category: text +#[node(Show)] +pub struct StrongNode { + /// The content to strongly emphasize. + #[positional] + #[required] + pub body: Content, -#[node] -impl StrongNode { /// The delta to apply on the font weight. /// /// ```example /// #set strong(delta: 0) /// No *effect!* /// ``` - pub const DELTA: i64 = 300; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } + #[settable] + #[default(300)] + pub delta: i64, } impl Show for StrongNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { - Ok(self.0.clone().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA)))) + Ok(self.body().styled(TextNode::DELTA, Delta(styles.get(Self::DELTA)))) } } @@ -147,11 +111,15 @@ impl Show for StrongNode { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Delta(pub i64); -castable! { +cast_from_value! { Delta, v: i64 => Self(v), } +cast_to_value! { + v: Delta => v.0.into() +} + impl Fold for Delta { type Output = i64; @@ -160,7 +128,6 @@ impl Fold for Delta { } } -/// # Emphasis /// Emphasizes content by setting it in italics. /// /// - If the current [text style]($func/text.style) is `{"normal"}`, @@ -185,34 +152,19 @@ impl Fold for Delta { /// enclose it in underscores (`_`). Note that this only works at word /// boundaries. To emphasize part of a word, you have to use the function. /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The content to emphasize. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct EmphNode(pub Content); - -#[node] -impl EmphNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "body" => Some(Value::Content(self.0.clone())), - _ => None, - } - } +/// Display: Emphasis +/// Category: text +#[node(Show)] +pub struct EmphNode { + /// The content to emphasize. + #[positional] + #[required] + pub body: Content, } impl Show for EmphNode { fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> { - Ok(self.0.clone().styled(TextNode::EMPH, Toggle)) + Ok(self.body().styled(TextNode::EMPH, Toggle)) } } @@ -220,6 +172,15 @@ impl Show for EmphNode { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Toggle; +cast_from_value! { + Toggle, + _: Value => Self, +} + +cast_to_value! { + _: Toggle => Value::None +} + impl Fold for Toggle { type Output = bool; @@ -228,7 +189,6 @@ impl Fold for Toggle { } } -/// # Lowercase /// Convert text or content to lowercase. /// /// ## Example @@ -242,14 +202,13 @@ impl Fold for Toggle { /// - text: `ToCase` (positional, required) /// The text to convert to lowercase. /// -/// ## Category -/// text +/// Display: Lowercase +/// Category: text #[func] pub fn lower(args: &mut Args) -> SourceResult<Value> { case(Case::Lower, args) } -/// # Uppercase /// Convert text or content to uppercase. /// /// ## Example @@ -263,8 +222,8 @@ pub fn lower(args: &mut Args) -> SourceResult<Value> { /// - text: `ToCase` (positional, required) /// The text to convert to uppercase. /// -/// ## Category -/// text +/// Display: Uppercase +/// Category: text #[func] pub fn upper(args: &mut Args) -> SourceResult<Value> { case(Case::Upper, args) @@ -272,21 +231,22 @@ pub fn upper(args: &mut Args) -> SourceResult<Value> { /// Change the case of text. fn case(case: Case, args: &mut Args) -> SourceResult<Value> { - let Spanned { v, span } = args.expect("string or content")?; - Ok(match v { - Value::Str(v) => Value::Str(case.apply(&v).into()), - Value::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), - v => bail!(span, "expected string or content, found {}", v.type_name()), + Ok(match args.expect("string or content")? { + ToCase::Str(v) => Value::Str(case.apply(&v).into()), + ToCase::Content(v) => Value::Content(v.styled(TextNode::CASE, Some(case))), }) } /// A value whose case can be changed. -struct ToCase; +enum ToCase { + Str(Str), + Content(Content), +} -castable! { +cast_from_value! { ToCase, - _: Str => Self, - _: Content => Self, + v: Str => Self::Str(v), + v: Content => Self::Content(v), } /// A case transformation on text. @@ -308,7 +268,19 @@ impl Case { } } -/// # Small Capitals +cast_from_value! { + Case, + "lower" => Self::Lower, + "upper" => Self::Upper, +} + +cast_to_value! { + v: Case => Value::from(match v { + Case::Lower => "lower", + Case::Upper => "upper", + }) +} + /// Display text in small capitals. /// /// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts @@ -336,15 +308,14 @@ impl Case { /// - text: `Content` (positional, required) /// The text to display to small capitals. /// -/// ## Category -/// text +/// Display: Small Capitals +/// Category: text #[func] pub fn smallcaps(args: &mut Args) -> SourceResult<Value> { let body: Content = args.expect("content")?; Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true))) } -/// # Blind Text /// Create blind text. /// /// This function yields a Latin-like _Lorem Ipsum_ blind text with the given @@ -367,8 +338,8 @@ pub fn smallcaps(args: &mut Args) -> SourceResult<Value> { /// /// - returns: string /// -/// ## Category -/// text +/// Display: Blind Text +/// Category: text #[func] pub fn lorem(args: &mut Args) -> SourceResult<Value> { let words: usize = args.expect("number of words")?; diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index bdd2d0c2..cde0163e 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -22,7 +22,6 @@ use typst::font::{FontMetrics, FontStretch, FontStyle, FontWeight, VerticalFontM use crate::layout::ParNode; use crate::prelude::*; -/// # Text /// Customize the look and layout of text in a variety of ways. /// /// This function is used often, both with set rules and directly. While the set @@ -62,26 +61,50 @@ use crate::prelude::*; /// - body: `Content` (positional, required) /// Content in which all text is styled according to the other arguments. /// -/// ## Category -/// text -#[func] -#[capable] -#[derive(Clone, Hash)] -pub struct TextNode(pub EcoString); +/// Display: Text +/// Category: text +#[node(Construct)] +#[set({ + if let Some(family) = args.named("family")? { + styles.set(Self::FAMILY, family); + } else { + let mut count = 0; + let mut content = false; + for item in args.items.iter().filter(|item| item.name.is_none()) { + if EcoString::is(&item.value) { + count += 1; + } else if <Content as Cast<Spanned<Value>>>::is(&item.value) { + content = true; + } + } -impl TextNode { - /// Create a new packed text node. - pub fn packed(text: impl Into<EcoString>) -> Content { - Self(text.into()).pack() + // Skip the final string if it's needed as the body. + if constructor && !content && count > 0 { + count -= 1; + } + + if count > 0 { + let mut list = Vec::with_capacity(count); + for _ in 0..count { + list.push(args.find()?.unwrap()); + } + + styles.set(Self::FAMILY, FallbackList(list)); + } } -} +})] +pub struct TextNode { + /// The text. + #[positional] + #[required] + #[skip] + pub text: EcoString, -#[node] -impl TextNode { /// A prioritized sequence of font families. - #[property(skip, referenced)] - pub const FAMILY: FallbackList = - FallbackList(vec![FontFamily::new("Linux Libertine")]); + #[settable] + #[skip] + #[default(FallbackList(vec![FontFamily::new("Linux Libertine")]))] + pub family: FallbackList, /// Whether to allow last resort font fallback when the primary font list /// contains no match. This lets Typst search through all available fonts @@ -100,7 +123,9 @@ impl TextNode { /// #set text(fallback: false) /// هذا عربي /// ``` - pub const FALLBACK: bool = true; + #[settable] + #[default(true)] + pub fallback: bool, /// The desired font style. /// @@ -119,7 +144,9 @@ impl TextNode { /// #text("Linux Libertine", style: "italic")[Italic] /// #text("DejaVu Sans", style: "oblique")[Oblique] /// ``` - pub const STYLE: FontStyle = FontStyle::Normal; + #[settable] + #[default(FontStyle::Normal)] + pub style: FontStyle, /// The desired thickness of the font's glyphs. Accepts an integer between /// `{100}` and `{900}` or one of the predefined weight names. When the @@ -138,7 +165,9 @@ impl TextNode { /// #text(weight: 500)[Medium] \ /// #text(weight: "bold")[Bold] /// ``` - pub const WEIGHT: FontWeight = FontWeight::REGULAR; + #[settable] + #[default(FontWeight::REGULAR)] + pub weight: FontWeight, /// The desired width of the glyphs. Accepts a ratio between `{50%}` and /// `{200%}`. When the desired weight is not available, Typst selects the @@ -148,7 +177,9 @@ impl TextNode { /// #text(stretch: 75%)[Condensed] \ /// #text(stretch: 100%)[Normal] /// ``` - pub const STRETCH: FontStretch = FontStretch::NORMAL; + #[settable] + #[default(FontStretch::NORMAL)] + pub stretch: FontStretch, /// The size of the glyphs. This value forms the basis of the `em` unit: /// `{1em}` is equivalent to the font size. @@ -160,8 +191,11 @@ impl TextNode { /// #set text(size: 20pt) /// very #text(1.5em)[big] text /// ``` - #[property(shorthand, fold)] - pub const SIZE: TextSize = Abs::pt(11.0); + #[settable] + #[shorthand] + #[fold] + #[default(Abs::pt(11.0))] + pub size: TextSize, /// The glyph fill color. /// @@ -169,8 +203,10 @@ impl TextNode { /// #set text(fill: red) /// This text is red. /// ``` - #[property(shorthand)] - pub const FILL: Paint = Color::BLACK.into(); + #[shorthand] + #[settable] + #[default(Color::BLACK.into())] + pub fill: Paint, /// The amount of space that should be added between characters. /// @@ -178,8 +214,10 @@ impl TextNode { /// #set text(tracking: 1.5pt) /// Distant text. /// ``` - #[property(resolve)] - pub const TRACKING: Length = Length::zero(); + #[settable] + #[resolve] + #[default(Length::zero())] + pub tracking: Length, /// The amount of space between words. /// @@ -190,8 +228,10 @@ impl TextNode { /// #set text(spacing: 200%) /// Text with distant words. /// ``` - #[property(resolve)] - pub const SPACING: Rel<Length> = Rel::one(); + #[settable] + #[resolve] + #[default(Rel::one())] + pub spacing: Rel<Length>, /// An amount to shift the text baseline by. /// @@ -199,8 +239,10 @@ impl TextNode { /// A #text(baseline: 3pt)[lowered] /// word. /// ``` - #[property(resolve)] - pub const BASELINE: Length = Length::zero(); + #[settable] + #[resolve] + #[default(Length::zero())] + pub baseline: Length, /// Whether certain glyphs can hang over into the margin in justified text. /// This can make justification visually more pleasing. @@ -222,7 +264,9 @@ impl TextNode { /// margin, making the paragraph's /// edge less clear. /// ``` - pub const OVERHANG: bool = true; + #[settable] + #[default(true)] + pub overhang: bool, /// The top end of the conceptual frame around the text used for layout and /// positioning. This affects the size of containers that hold text. @@ -237,7 +281,9 @@ impl TextNode { /// #set text(top-edge: "cap-height") /// #rect(fill: aqua)[Typst] /// ``` - pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight); + #[settable] + #[default(TextEdge::Metric(VerticalFontMetric::CapHeight))] + pub top_edge: TextEdge, /// The bottom end of the conceptual frame around the text used for layout /// and positioning. This affects the size of containers that hold text. @@ -252,7 +298,9 @@ impl TextNode { /// #set text(bottom-edge: "descender") /// #rect(fill: aqua)[Typst] /// ``` - pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline); + #[settable] + #[default(TextEdge::Metric(VerticalFontMetric::Baseline))] + pub bottom_edge: TextEdge, /// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639) /// @@ -271,12 +319,16 @@ impl TextNode { /// = Einleitung /// In diesem Dokument, ... /// ``` - pub const LANG: Lang = Lang::ENGLISH; + #[settable] + #[default(Lang::ENGLISH)] + pub lang: Lang, /// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) /// /// This lets the text processing pipeline make more informed choices. - pub const REGION: Option<Region> = None; + #[settable] + #[default(None)] + pub region: Option<Region>, /// The dominant direction for text and inline objects. Possible values are: /// @@ -302,8 +354,10 @@ impl TextNode { /// #set text(dir: rtl) /// هذا عربي. /// ``` - #[property(resolve)] - pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto); + #[settable] + #[resolve] + #[default(HorizontalDir(Smart::Auto))] + pub dir: HorizontalDir, /// Whether to hyphenate text to improve line breaking. When `{auto}`, text /// will be hyphenated if and only if justification is enabled. @@ -322,8 +376,10 @@ impl TextNode { /// enabling hyphenation can /// improve justification. /// ``` - #[property(resolve)] - pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto); + #[settable] + #[resolve] + #[default(Hyphenate(Smart::Auto))] + pub hyphenate: Hyphenate, /// Whether to apply kerning. /// @@ -340,7 +396,9 @@ impl TextNode { /// #set text(kerning: false) /// Totally /// ``` - pub const KERNING: bool = true; + #[settable] + #[default(true)] + pub kerning: bool, /// Whether to apply stylistic alternates. /// @@ -355,14 +413,18 @@ impl TextNode { /// #set text(alternates: true) /// 0, a, g, ß /// ``` - pub const ALTERNATES: bool = false; + #[settable] + #[default(false)] + pub alternates: bool, /// Which stylistic set to apply. Font designers can categorize alternative /// glyphs forms into stylistic sets. As this value is highly font-specific, /// you need to consult your font to know which sets are available. When set /// to an integer between `{1}` and `{20}`, enables the corresponding /// OpenType font feature from `ss01`, ..., `ss20`. - pub const STYLISTIC_SET: Option<StylisticSet> = None; + #[settable] + #[default(None)] + pub stylistic_set: Option<StylisticSet>, /// Whether standard ligatures are active. /// @@ -378,15 +440,21 @@ impl TextNode { /// #set text(ligatures: false) /// A fine ligature. /// ``` - pub const LIGATURES: bool = true; + #[settable] + #[default(true)] + pub ligatures: bool, /// Whether ligatures that should be used sparingly are active. Setting this /// to `{true}` enables the OpenType `dlig` font feature. - pub const DISCRETIONARY_LIGATURES: bool = false; + #[settable] + #[default(false)] + pub discretionary_ligatures: bool, /// Whether historical ligatures are active. Setting this to `{true}` /// enables the OpenType `hlig` font feature. - pub const HISTORICAL_LIGATURES: bool = false; + #[settable] + #[default(false)] + pub historical_ligatures: bool, /// Which kind of numbers / figures to select. When set to `{auto}`, the /// default numbers for the font are used. @@ -399,7 +467,9 @@ impl TextNode { /// #set text(number-type: "old-style") /// Number 9. /// ``` - pub const NUMBER_TYPE: Smart<NumberType> = Smart::Auto; + #[settable] + #[default(Smart::Auto)] + pub number_type: Smart<NumberType>, /// The width of numbers / figures. When set to `{auto}`, the default /// numbers for the font are used. @@ -414,7 +484,9 @@ impl TextNode { /// A 12 B 34. \ /// A 56 B 78. /// ``` - pub const NUMBER_WIDTH: Smart<NumberWidth> = Smart::Auto; + #[settable] + #[default(Smart::Auto)] + pub number_width: Smart<NumberWidth>, /// Whether to have a slash through the zero glyph. Setting this to `{true}` /// enables the OpenType `zero` font feature. @@ -422,7 +494,9 @@ impl TextNode { /// ```example /// 0, #text(slashed-zero: true)[0] /// ``` - pub const SLASHED_ZERO: bool = false; + #[settable] + #[default(false)] + pub slashed_zero: bool, /// Whether to turns numbers into fractions. Setting this to `{true}` /// enables the OpenType `frac` font feature. @@ -431,7 +505,9 @@ impl TextNode { /// 1/2 \ /// #text(fractions: true)[1/2] /// ``` - pub const FRACTIONS: bool = false; + #[settable] + #[default(false)] + pub fractions: bool, /// Raw OpenType features to apply. /// @@ -445,74 +521,59 @@ impl TextNode { /// #set text(features: ("frac",)) /// 1/2 /// ``` - #[property(fold)] - pub const FEATURES: FontFeatures = FontFeatures(vec![]); + #[settable] + #[fold] + #[default(FontFeatures(vec![]))] + pub features: FontFeatures, /// A delta to apply on the font weight. - #[property(skip, fold)] - pub const DELTA: Delta = 0; + #[settable] + #[fold] + #[skip] + #[default(0)] + pub delta: Delta, + /// Whether the font style should be inverted. - #[property(skip, fold)] - pub const EMPH: Toggle = false; + #[settable] + #[fold] + #[skip] + #[default(false)] + pub emph: Toggle, + /// A case transformation that should be applied to the text. - #[property(skip)] - pub const CASE: Option<Case> = None; + #[settable] + #[skip] + #[default(None)] + pub case: Option<Case>, + /// Whether small capital glyphs should be used. ("smcp") - #[property(skip)] - pub const SMALLCAPS: bool = false; + #[settable] + #[skip] + #[default(false)] + pub smallcaps: bool, + /// Decorative lines. - #[property(skip, fold)] - pub const DECO: Decoration = vec![]; + #[settable] + #[fold] + #[skip] + #[default(vec![])] + pub deco: Decoration, +} + +impl TextNode { + /// Create a new packed text node. + pub fn packed(text: impl Into<EcoString>) -> Content { + Self::new(text.into()).pack() + } +} +impl Construct for TextNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { // The text constructor is special: It doesn't create a text node. // Instead, it leaves the passed argument structurally unchanged, but // styles all text in it. args.expect("body") } - - fn set(...) { - if let Some(family) = args.named("family")? { - styles.set(Self::FAMILY, family); - } else { - let mut count = 0; - let mut content = false; - for item in args.items.iter().filter(|item| item.name.is_none()) { - if EcoString::is(&item.value) { - count += 1; - } else if <Content as Cast<Spanned<Value>>>::is(&item.value) { - content = true; - } - } - - // Skip the final string if it's needed as the body. - if constructor && !content && count > 0 { - count -= 1; - } - - if count > 0 { - let mut list = Vec::with_capacity(count); - for _ in 0..count { - list.push(args.find()?.unwrap()); - } - - styles.set(Self::FAMILY, FallbackList(list)); - } - } - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "text" => Some(Value::Str(self.0.clone().into())), - _ => None, - } - } -} - -impl Debug for TextNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "Text({:?})", self.0) - } } /// A lowercased font family like "arial". @@ -537,21 +598,29 @@ impl Debug for FontFamily { } } -castable! { +cast_from_value! { FontFamily, string: EcoString => Self::new(&string), } +cast_to_value! { + v: FontFamily => v.0.into() +} + /// Font family fallback list. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct FallbackList(pub Vec<FontFamily>); -castable! { +cast_from_value! { FallbackList, family: FontFamily => Self(vec![family]), values: Array => Self(values.into_iter().map(|v| v.cast()).collect::<StrResult<_>>()?), } +cast_to_value! { + v: FallbackList => v.0.into() +} + /// The size of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct TextSize(pub Length); @@ -564,11 +633,15 @@ impl Fold for TextSize { } } -castable! { +cast_from_value! { TextSize, v: Length => Self(v), } +cast_to_value! { + v: TextSize => v.0.into() +} + /// Specifies the bottom or top edge of text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum TextEdge { @@ -588,34 +661,37 @@ impl TextEdge { } } -castable! { +cast_from_value! { TextEdge, + v: VerticalFontMetric => Self::Metric(v), v: Length => Self::Length(v), - /// The font's ascender, which typically exceeds the height of all glyphs. - "ascender" => Self::Metric(VerticalFontMetric::Ascender), - /// The approximate height of uppercase letters. - "cap-height" => Self::Metric(VerticalFontMetric::CapHeight), - /// The approximate height of non-ascending lowercase letters. - "x-height" => Self::Metric(VerticalFontMetric::XHeight), - /// The baseline on which the letters rest. - "baseline" => Self::Metric(VerticalFontMetric::Baseline), - /// The font's ascender, which typically exceeds the depth of all glyphs. - "descender" => Self::Metric(VerticalFontMetric::Descender), +} + +cast_to_value! { + v: TextEdge => match v { + TextEdge::Metric(metric) => metric.into(), + TextEdge::Length(length) => length.into(), + } } /// The direction of text and inline objects in their line. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct HorizontalDir(pub Smart<Dir>); -castable! { +cast_from_value! { HorizontalDir, - _: AutoValue => Self(Smart::Auto), - dir: Dir => match dir.axis() { - Axis::X => Self(Smart::Custom(dir)), - Axis::Y => Err("must be horizontal")?, + v: Smart<Dir> => { + if v.map_or(false, |dir| dir.axis() == Axis::Y) { + Err("must be horizontal")?; + } + Self(v) }, } +cast_to_value! { + v: HorizontalDir => v.0.into() +} + impl Resolve for HorizontalDir { type Output = Dir; @@ -631,10 +707,13 @@ impl Resolve for HorizontalDir { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct Hyphenate(pub Smart<bool>); -castable! { +cast_from_value! { Hyphenate, - _: AutoValue => Self(Smart::Auto), - v: bool => Self(Smart::Custom(v)), + v: Smart<bool> => Self(v), +} + +cast_to_value! { + v: Hyphenate => v.0.into() } impl Resolve for Hyphenate { @@ -664,7 +743,7 @@ impl StylisticSet { } } -castable! { +cast_from_value! { StylisticSet, v: i64 => match v { 1 ..= 20 => Self::new(v as u8), @@ -672,6 +751,10 @@ castable! { }, } +cast_to_value! { + v: StylisticSet => v.0.into() +} + /// Which kind of numbers / figures to select. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum NumberType { @@ -681,16 +764,23 @@ pub enum NumberType { OldStyle, } -castable! { +cast_from_value! { NumberType, /// Numbers that fit well with capital text (the OpenType `lnum` /// font feature). "lining" => Self::Lining, - /// Numbers that fit well into a flow of upper- and lowercase text (the + // Numbers that fit well into a flow of upper- and lowercase text (the /// OpenType `onum` font feature). "old-style" => Self::OldStyle, } +cast_to_value! { + v: NumberType => Value::from(match v { + NumberType::Lining => "lining", + NumberType::OldStyle => "old-style", + }) +} + /// The width of numbers / figures. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum NumberWidth { @@ -700,7 +790,7 @@ pub enum NumberWidth { Tabular, } -castable! { +cast_from_value! { NumberWidth, /// Numbers with glyph-specific widths (the OpenType `pnum` font feature). "proportional" => Self::Proportional, @@ -708,11 +798,18 @@ castable! { "tabular" => Self::Tabular, } +cast_to_value! { + v: NumberWidth => Value::from(match v { + NumberWidth::Proportional => "proportional", + NumberWidth::Tabular => "tabular", + }) +} + /// OpenType font features settings. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] pub struct FontFeatures(pub Vec<(Tag, u32)>); -castable! { +cast_from_value! { FontFeatures, values: Array => Self(values .into_iter() @@ -731,6 +828,18 @@ castable! { .collect::<StrResult<_>>()?), } +cast_to_value! { + v: FontFeatures => Value::Dict( + v.0.into_iter() + .map(|(tag, num)| { + let bytes = tag.to_bytes(); + let key = std::str::from_utf8(&bytes).unwrap_or_default(); + (key.into(), num.into()) + }) + .collect(), + ) +} + impl Fold for FontFeatures { type Output = Self; diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index c0a7e11c..1c602871 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -2,7 +2,6 @@ use typst::syntax::is_newline; use crate::prelude::*; -/// # Smart Quote /// A language-aware quote that reacts to its context. /// /// Automatically turns into an appropriate opening or closing quote based on @@ -23,21 +22,15 @@ use crate::prelude::*; /// This function also has dedicated syntax: The normal quote characters /// (`'` and `"`). Typst automatically makes your quotes smart. /// -/// ## Parameters -/// - double: `bool` (named) -/// Whether this should be a double quote. -/// -/// ## Category -/// text -#[func] -#[capable] -#[derive(Debug, Hash)] +/// Display: Smart Quote +/// Category: text +#[node] pub struct SmartQuoteNode { + /// Whether this should be a double quote. + #[named] + #[default(true)] pub double: bool, -} -#[node] -impl SmartQuoteNode { /// Whether smart quotes are enabled. /// /// To disable smartness for a single quote, you can also escape it with a @@ -48,19 +41,9 @@ impl SmartQuoteNode { /// /// These are "dumb" quotes. /// ``` - pub const ENABLED: bool = true; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - let double = args.named("double")?.unwrap_or(true); - Ok(Self { double }.pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "double" => Some(Value::Bool(self.double)), - _ => None, - } - } + #[settable] + #[default(true)] + pub enabled: bool, } /// State machine for smart quote substitution. diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index ec11582c..cdaefd06 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -9,7 +9,6 @@ use super::{ use crate::layout::BlockNode; use crate::prelude::*; -/// # Raw Text / Code /// Raw text with optional syntax highlighting. /// /// Displays the text verbatim and in a monospace font. This is typically used @@ -35,71 +34,64 @@ use crate::prelude::*; /// ``` /// ```` /// -/// ## Parameters -/// - text: `EcoString` (positional, required) -/// The raw text. -/// -/// You can also use raw blocks creatively to create custom syntaxes for -/// your automations. -/// -/// ````example -/// // Parse numbers in raw blocks with the -/// // `mydsl` tag and sum them up. -/// #show raw.where(lang: "mydsl"): it => { -/// let sum = 0 -/// for part in it.text.split("+") { -/// sum += int(part.trim()) -/// } -/// sum -/// } -/// -/// ```mydsl -/// 1 + 2 + 3 + 4 + 5 -/// ``` -/// ```` -/// -/// - block: `bool` (named) -/// Whether the raw text is displayed as a separate block. -/// -/// ````example -/// // Display inline code in a small box -/// // that retains the correct baseline. -/// #show raw.where(block: false): box.with( -/// fill: luma(240), -/// inset: (x: 3pt, y: 0pt), -/// outset: (y: 3pt), -/// radius: 2pt, -/// ) -/// -/// // Display block code in a larger block -/// // with more padding. -/// #show raw.where(block: true): block.with( -/// fill: luma(240), -/// inset: 10pt, -/// radius: 4pt, -/// ) -/// -/// With `rg`, you can search through your files quickly. -/// -/// ```bash -/// rg "Hello World" -/// ``` -/// ```` -/// -/// ## Category -/// text -#[func] -#[capable(Prepare, Show, Finalize)] -#[derive(Debug, Hash)] +/// Display: Raw Text / Code +/// Category: text +#[node(Prepare, Show, Finalize)] pub struct RawNode { /// The raw text. + /// + /// You can also use raw blocks creatively to create custom syntaxes for + /// your automations. + /// + /// ````example + /// // Parse numbers in raw blocks with the + /// // `mydsl` tag and sum them up. + /// #show raw.where(lang: "mydsl"): it => { + /// let sum = 0 + /// for part in it.text.split("+") { + /// sum += int(part.trim()) + /// } + /// sum + /// } + /// + /// ```mydsl + /// 1 + 2 + 3 + 4 + 5 + /// ``` + /// ```` + #[positional] + #[required] pub text: EcoString, + /// Whether the raw text is displayed as a separate block. + /// + /// ````example + /// // Display inline code in a small box + /// // that retains the correct baseline. + /// #show raw.where(block: false): box.with( + /// fill: luma(240), + /// inset: (x: 3pt, y: 0pt), + /// outset: (y: 3pt), + /// radius: 2pt, + /// ) + /// + /// // Display block code in a larger block + /// // with more padding. + /// #show raw.where(block: true): block.with( + /// fill: luma(240), + /// inset: 10pt, + /// radius: 4pt, + /// ) + /// + /// With `rg`, you can search through your files quickly. + /// + /// ```bash + /// rg "Hello World" + /// ``` + /// ```` + #[named] + #[default(false)] pub block: bool, -} -#[node] -impl RawNode { /// The language to syntax-highlight in. /// /// Apart from typical language tags known from Markdown, this supports the @@ -111,24 +103,9 @@ impl RawNode { /// This is *Typst!* /// ``` /// ```` - #[property(referenced)] - pub const LANG: Option<EcoString> = None; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - text: args.expect("text")?, - block: args.named("block")?.unwrap_or(false), - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "text" => Some(Value::Str(self.text.clone().into())), - "block" => Some(Value::Bool(self.block)), - _ => None, - } - } + #[settable] + #[default] + pub lang: Option<EcoString>, } impl Prepare for RawNode { @@ -138,19 +115,14 @@ impl Prepare for RawNode { mut this: Content, styles: StyleChain, ) -> SourceResult<Content> { - this.push_field( - "lang", - match styles.get(Self::LANG) { - Some(lang) => Value::Str(lang.clone().into()), - None => Value::None, - }, - ); + this.push_field("lang", styles.get(Self::LANG).clone()); Ok(this) } } impl Show for RawNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> { + let text = self.text(); let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings @@ -161,8 +133,8 @@ impl Show for RawNode { let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) { let root = match lang.as_deref() { - Some("typc") => syntax::parse_code(&self.text), - _ => syntax::parse(&self.text), + Some("typc") => syntax::parse_code(&text), + _ => syntax::parse(&text), }; let mut seq = vec![]; @@ -172,7 +144,7 @@ impl Show for RawNode { vec![], &highlighter, &mut |node, style| { - seq.push(styled(&self.text[node.range()], foreground, style)); + seq.push(styled(&text[node.range()], foreground, style)); }, ); @@ -182,9 +154,9 @@ impl Show for RawNode { { let mut seq = vec![]; let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME); - for (i, line) in self.text.lines().enumerate() { + for (i, line) in text.lines().enumerate() { if i != 0 { - seq.push(LinebreakNode { justify: false }.pack()); + seq.push(LinebreakNode::new().pack()); } for (style, piece) in @@ -196,16 +168,11 @@ impl Show for RawNode { Content::sequence(seq) } else { - TextNode::packed(self.text.clone()) + TextNode::packed(text) }; - if self.block { - realized = BlockNode { - body: realized, - width: Smart::Auto, - height: Smart::Auto, - } - .pack(); + if self.block() { + realized = BlockNode::new().with_body(realized).pack(); } Ok(realized) diff --git a/library/src/text/shaping.rs b/library/src/text/shaping.rs index feb9b24b..709cce25 100644 --- a/library/src/text/shaping.rs +++ b/library/src/text/shaping.rs @@ -154,7 +154,7 @@ impl<'a> ShapedText<'a> { for family in families(self.styles) { if let Some(font) = world .book() - .select(family, self.variant) + .select(family.as_str(), self.variant) .and_then(|id| world.font(id)) { expand(&font); @@ -209,7 +209,7 @@ impl<'a> ShapedText<'a> { let world = vt.world(); let font = world .book() - .select(family, self.variant) + .select(family.as_str(), self.variant) .and_then(|id| world.font(id))?; let ttf = font.ttf(); let glyph_id = ttf.glyph_index('-')?; @@ -351,7 +351,7 @@ fn shape_segment<'a>( ctx: &mut ShapingContext, base: usize, text: &str, - mut families: impl Iterator<Item = &'a str> + Clone, + mut families: impl Iterator<Item = FontFamily> + Clone, ) { // Fonts dont have newlines and tabs. if text.chars().all(|c| c == '\n' || c == '\t') { @@ -362,7 +362,7 @@ fn shape_segment<'a>( let world = ctx.vt.world(); let book = world.book(); let mut selection = families.find_map(|family| { - book.select(family, ctx.variant) + book.select(family.as_str(), ctx.variant) .and_then(|id| world.font(id)) .filter(|font| !ctx.used.contains(font)) }); @@ -549,7 +549,7 @@ pub fn variant(styles: StyleChain) -> FontVariant { } /// Resolve a prioritized iterator over the font families. -pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone { +pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone { const FALLBACKS: &[&str] = &[ "linux libertine", "twitter color emoji", @@ -562,9 +562,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone { styles .get(TextNode::FAMILY) .0 - .iter() - .map(|family| family.as_str()) - .chain(tail.iter().copied()) + .into_iter() + .chain(tail.iter().copied().map(FontFamily::new)) } /// Collect the tags of the OpenType features to apply. diff --git a/library/src/text/shift.rs b/library/src/text/shift.rs index d6809591..105953b6 100644 --- a/library/src/text/shift.rs +++ b/library/src/text/shift.rs @@ -3,7 +3,6 @@ use typst::model::SequenceNode; use super::{variant, SpaceNode, TextNode, TextSize}; use crate::prelude::*; -/// # Subscript /// Set text in subscript. /// /// The text is rendered smaller and its baseline is lowered. @@ -13,19 +12,15 @@ use crate::prelude::*; /// Revenue#sub[yearly] /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The text to display in subscript. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct SubNode(pub Content); - -#[node] -impl SubNode { +/// Display: Subscript +/// Category: text +#[node(Show)] +pub struct SubNode { + /// The text to display in subscript. + #[positional] + #[required] + pub body: Content, + /// Whether to prefer the dedicated subscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to subscript @@ -36,19 +31,23 @@ impl SubNode { /// N#sub(typographic: true)[1] /// N#sub(typographic: false)[1] /// ``` - pub const TYPOGRAPHIC: bool = true; + #[settable] + #[default(true)] + pub typographic: bool, + /// The baseline shift for synthetic subscripts. Does not apply if /// `typographic` is true and the font has subscript codepoints for the /// given `body`. - pub const BASELINE: Length = Em::new(0.2).into(); + #[settable] + #[default(Em::new(0.2).into())] + pub baseline: Length, + /// The font size for synthetic subscripts. Does not apply if /// `typographic` is true and the font has subscript codepoints for the /// given `body`. - pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } + #[settable] + #[default(TextSize(Em::new(0.6).into()))] + pub size: TextSize, } impl Show for SubNode { @@ -58,9 +57,10 @@ impl Show for SubNode { _: &Content, styles: StyleChain, ) -> SourceResult<Content> { + let body = self.body(); let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { - if let Some(text) = search_text(&self.0, true) { + if let Some(text) = search_text(&body, true) { if is_shapable(vt, &text, styles) { transformed = Some(TextNode::packed(text)); } @@ -71,12 +71,11 @@ impl Show for SubNode { let mut map = StyleMap::new(); map.set(TextNode::BASELINE, styles.get(Self::BASELINE)); map.set(TextNode::SIZE, styles.get(Self::SIZE)); - self.0.clone().styled_with_map(map) + body.styled_with_map(map) })) } } -/// # Superscript /// Set text in superscript. /// /// The text is rendered smaller and its baseline is raised. @@ -86,19 +85,15 @@ impl Show for SubNode { /// 1#super[st] try! /// ``` /// -/// ## Parameters -/// - body: `Content` (positional, required) -/// The text to display in superscript. -/// -/// ## Category -/// text -#[func] -#[capable(Show)] -#[derive(Debug, Hash)] -pub struct SuperNode(pub Content); - -#[node] -impl SuperNode { +/// Display: Superscript +/// Category: text +#[node(Show)] +pub struct SuperNode { + /// The text to display in superscript. + #[positional] + #[required] + pub body: Content, + /// Whether to prefer the dedicated superscript characters of the font. /// /// If this is enabled, Typst first tries to transform the text to @@ -109,19 +104,23 @@ impl SuperNode { /// N#super(typographic: true)[1] /// N#super(typographic: false)[1] /// ``` - pub const TYPOGRAPHIC: bool = true; + #[settable] + #[default(true)] + pub typographic: bool, + /// The baseline shift for synthetic superscripts. Does not apply if /// `typographic` is true and the font has superscript codepoints for the /// given `body`. - pub const BASELINE: Length = Em::new(-0.5).into(); + #[settable] + #[default(Em::new(-0.5).into())] + pub baseline: Length, + /// The font size for synthetic superscripts. Does not apply if /// `typographic` is true and the font has superscript codepoints for the /// given `body`. - pub const SIZE: TextSize = TextSize(Em::new(0.6).into()); - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("body")?).pack()) - } + #[settable] + #[default(TextSize(Em::new(0.6).into()))] + pub size: TextSize, } impl Show for SuperNode { @@ -131,9 +130,10 @@ impl Show for SuperNode { _: &Content, styles: StyleChain, ) -> SourceResult<Content> { + let body = self.body(); let mut transformed = None; if styles.get(Self::TYPOGRAPHIC) { - if let Some(text) = search_text(&self.0, false) { + if let Some(text) = search_text(&body, false) { if is_shapable(vt, &text, styles) { transformed = Some(TextNode::packed(text)); } @@ -144,7 +144,7 @@ impl Show for SuperNode { let mut map = StyleMap::new(); map.set(TextNode::BASELINE, styles.get(Self::BASELINE)); map.set(TextNode::SIZE, styles.get(Self::SIZE)); - self.0.clone().styled_with_map(map) + body.styled_with_map(map) })) } } @@ -154,12 +154,12 @@ impl Show for SuperNode { fn search_text(content: &Content, sub: bool) -> Option<EcoString> { if content.is::<SpaceNode>() { Some(' '.into()) - } else if let Some(text) = content.to::<TextNode>() { - convert_script(&text.0, sub) + } else if let Some(node) = content.to::<TextNode>() { + convert_script(&node.text(), sub) } else if let Some(seq) = content.to::<SequenceNode>() { let mut full = EcoString::new(); - for item in seq.0.iter() { - match search_text(item, sub) { + for item in seq.children() { + match search_text(&item, sub) { Some(text) => full.push_str(&text), None => return None, } |
