diff options
Diffstat (limited to 'library/src/text/mod.rs')
| -rw-r--r-- | library/src/text/mod.rs | 373 |
1 files changed, 241 insertions, 132 deletions
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; |
