summaryrefslogtreecommitdiff
path: root/library/src/text
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-07 15:17:13 +0100
commit25b5bd117529cd04bb789e1988eb3a3db8025a0e (patch)
tree2fbb4650903123da047a1f1f11a0abda95286e12 /library/src/text
parent6ab7760822ccd24b4ef126d4737d41f1be15fe19 (diff)
Fully untyped model
Diffstat (limited to 'library/src/text')
-rw-r--r--library/src/text/deco.rs180
-rw-r--r--library/src/text/misc.rs209
-rw-r--r--library/src/text/mod.rs373
-rw-r--r--library/src/text/quotes.rs35
-rw-r--r--library/src/text/raw.rs163
-rw-r--r--library/src/text/shaping.rs15
-rw-r--r--library/src/text/shift.rs100
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,
}