From ee732468c7487c81aa6470571077988b75d36ebb Mon Sep 17 00:00:00 2001 From: Laurenz Date: Wed, 21 Dec 2022 00:16:07 +0100 Subject: [PATCH] Document text category --- library/src/basics/table.rs | 4 + library/src/layout/columns.rs | 15 ++ library/src/layout/container.rs | 14 ++ library/src/layout/grid.rs | 27 ++ library/src/layout/hide.rs | 7 + library/src/layout/pad.rs | 7 + library/src/layout/page.rs | 14 ++ library/src/layout/par.rs | 2 +- library/src/layout/place.rs | 7 + library/src/layout/repeat.rs | 7 + library/src/layout/spacing.rs | 34 ++- library/src/layout/stack.rs | 20 ++ library/src/layout/transform.rs | 22 ++ library/src/math/matrix.rs | 18 ++ library/src/meta/link.rs | 6 +- library/src/text/misc.rs | 122 ++++++++- library/src/text/mod.rs | 421 +++++++++++++++++++++++++++++--- library/src/text/quotes.rs | 42 +++- library/src/text/raw.rs | 88 ++++++- library/src/text/symbol.rs | 42 +++- library/src/visualize/shape.rs | 10 + macros/src/func.rs | 16 +- src/font/mod.rs | 4 +- src/font/variant.rs | 6 +- src/model/cast.rs | 6 +- tests/typ/layout/spacing.typ | 2 +- tests/typ/text/quotes.typ | 4 +- 27 files changed, 894 insertions(+), 73 deletions(-) diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs index dbb3d58e0..ed8df104e 100644 --- a/library/src/basics/table.rs +++ b/library/src/basics/table.rs @@ -67,6 +67,10 @@ impl TableNode { fn field(&self, name: &str) -> Option { match name { + "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), + "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), + "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), + "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), "cells" => Some(Value::Array( self.cells.iter().cloned().map(Value::Content).collect(), )), diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index c03ff433b..a5f67b940 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -65,6 +65,14 @@ impl ColumnsNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "count" => Some(Value::Int(self.count.get() as i64)), + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for ColumnsNode { @@ -186,6 +194,13 @@ impl ColbreakNode { let weak = args.named("weak")?.unwrap_or(false); Ok(Self { weak }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "weak" => Some(Value::Bool(self.weak)), + _ => None, + } + } } impl Behave for ColbreakNode { diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index 62e129b46..112c6f030 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -50,6 +50,13 @@ impl BoxNode { let body = args.eat::()?.unwrap_or_default(); Ok(Self { sizing: Axes::new(width, height), body }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for BoxNode { @@ -163,6 +170,13 @@ impl BlockNode { args.named("below")?.map(VNode::block_around).or(spacing), ); } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } } impl Layout for BlockNode { diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 85d464b12..eafe644fd 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -120,6 +120,19 @@ impl GridNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), + "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), + "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), + "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), + "cells" => Some(Value::Array( + self.cells.iter().cloned().map(Value::Content).collect(), + )), + _ => None, + } + } } impl Layout for GridNode { @@ -157,6 +170,20 @@ pub enum TrackSizing { Fractional(Fr), } +impl TrackSizing { + pub fn encode(self) -> Value { + match self { + Self::Auto => Value::Auto, + Self::Relative(rel) => Spacing::Relative(rel).encode(), + Self::Fractional(fr) => Spacing::Fractional(fr).encode(), + } + } + + pub fn encode_slice(vec: &[TrackSizing]) -> Value { + Value::Array(vec.iter().copied().map(Self::encode).collect()) + } +} + impl From for TrackSizing { fn from(spacing: Spacing) -> Self { match spacing { diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs index dec6dd1cb..6d168d35b 100644 --- a/library/src/layout/hide.rs +++ b/library/src/layout/hide.rs @@ -30,6 +30,13 @@ impl HideNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } } impl Layout for HideNode { diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 13b573bfc..bbd55225f 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -67,6 +67,13 @@ impl PadNode { let padding = Sides::new(left, top, right, bottom); Ok(Self { padding, body }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for PadNode { diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 105638a7e..8e1461266 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -64,6 +64,13 @@ impl PageNode { styles.set(Self::HEIGHT, Smart::Custom(paper.height().into())); } } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } } impl PageNode { @@ -190,6 +197,13 @@ impl PagebreakNode { let weak = args.named("weak")?.unwrap_or(false); Ok(Self { weak }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "weak" => Some(Value::Bool(self.weak)), + _ => None, + } + } } /// A header, footer, foreground or background definition. diff --git a/library/src/layout/par.rs b/library/src/layout/par.rs index f966d30be..412a279e8 100644 --- a/library/src/layout/par.rs +++ b/library/src/layout/par.rs @@ -467,7 +467,7 @@ fn collect<'a>( Segment::Text(c.len_utf8()) } else if let Some(node) = child.to::() { let prev = full.len(); - if styles.get(TextNode::SMART_QUOTES) { + if styles.get(SmartQuoteNode::ENABLED) { let lang = styles.get(TextNode::LANG); let region = styles.get(TextNode::REGION); let quotes = Quotes::from_lang(lang, region); diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index ed3d71bf3..406aa862e 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -33,6 +33,13 @@ impl PlaceNode { let out_of_flow = aligns.y.is_some(); Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns), out_of_flow).pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } } impl Layout for PlaceNode { diff --git a/library/src/layout/repeat.rs b/library/src/layout/repeat.rs index 864454c42..04610fca1 100644 --- a/library/src/layout/repeat.rs +++ b/library/src/layout/repeat.rs @@ -19,6 +19,13 @@ impl RepeatNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } } impl Layout for RepeatNode { diff --git a/library/src/layout/spacing.rs b/library/src/layout/spacing.rs index 91b36661d..35f6aa9b3 100644 --- a/library/src/layout/spacing.rs +++ b/library/src/layout/spacing.rs @@ -54,10 +54,18 @@ pub struct HNode { #[node] impl HNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let amount = args.expect("spacing")?; + let amount = args.expect("amount")?; let weak = args.named("weak")?.unwrap_or(false); Ok(Self { amount, weak }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "amount" => Some(self.amount.encode()), + "weak" => Some(Value::Bool(self.weak)), + _ => None, + } + } } impl HNode { @@ -159,6 +167,14 @@ impl VNode { }; Ok(node.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "amount" => Some(self.amount.encode()), + "weak" => Some(Value::Bool(self.weakness != 0)), + _ => None, + } + } } impl VNode { @@ -220,6 +236,22 @@ impl Spacing { pub fn is_fractional(self) -> bool { matches!(self, Self::Fractional(_)) } + + /// Encode into a value. + pub fn encode(self) -> Value { + match self { + Self::Relative(rel) => { + if rel.rel.is_zero() { + Value::Length(rel.abs) + } else if rel.abs.is_zero() { + Value::Ratio(rel.rel) + } else { + Value::Relative(rel) + } + } + Self::Fractional(fr) => Value::Fraction(fr), + } + } } impl From for Spacing { diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index f83f4e416..c423b1a3a 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -40,6 +40,26 @@ impl StackNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "dir" => Some(Value::dynamic(self.dir)), + "spacing" => Some(match self.spacing { + Some(spacing) => spacing.encode(), + None => Value::None, + }), + "items" => Some(Value::Array( + self.children + .iter() + .map(|child| match child { + StackChild::Spacing(spacing) => spacing.encode(), + StackChild::Block(content) => Value::Content(content.clone()), + }) + .collect(), + )), + _ => None, + } + } } impl Layout for StackNode { diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index 92a317808..57a6c0699 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -31,6 +31,7 @@ use crate::prelude::*; /// ### Example /// ``` /// Hello, world!#move(dy: -2pt)[!]#move(dy: 2pt)[!] +/// ``` /// /// - dx: Rel (named) /// The horizontal displacement of the content. @@ -61,6 +62,13 @@ impl MoveNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for MoveNode { @@ -130,6 +138,13 @@ impl RotateNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for RotateNode { @@ -209,6 +224,13 @@ impl ScaleNode { } .pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.body.clone())), + _ => None, + } + } } impl Layout for ScaleNode { diff --git a/library/src/math/matrix.rs b/library/src/math/matrix.rs index 92199774d..49527354a 100644 --- a/library/src/math/matrix.rs +++ b/library/src/math/matrix.rs @@ -22,6 +22,15 @@ impl VecNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.all()?).pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "elements" => { + Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect())) + } + _ => None, + } + } } impl Texify for VecNode { @@ -89,6 +98,15 @@ impl CasesNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.all()?).pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "branches" => { + Some(Value::Array(self.0.iter().cloned().map(Value::Content).collect())) + } + _ => None, + } + } } impl Texify for CasesNode { diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs index 501ccddb7..60d57a5fa 100644 --- a/library/src/meta/link.rs +++ b/library/src/meta/link.rs @@ -5,7 +5,9 @@ use crate::text::TextNode; /// Link to a URL or another location in the document. /// /// The link function makes its positional `body` argument clickable and links -/// it to the destination specified by the `dest` argument. +/// it to the destination specified by the `dest` argument. By default, links +/// are not styled any different from normal text. However, you can easily apply +/// a style of your choice with a show rule. /// /// ## Example /// ``` @@ -85,7 +87,7 @@ impl LinkNode { fn field(&self, name: &str) -> Option { match name { - "url" => Some(match &self.dest { + "dest" => Some(match &self.dest { Destination::Url(url) => Value::Str(url.clone().into()), Destination::Internal(loc) => Value::Dict(loc.encode()), }), diff --git a/library/src/text/misc.rs b/library/src/text/misc.rs index df40e52df..4cbc038e2 100644 --- a/library/src/text/misc.rs +++ b/library/src/text/misc.rs @@ -27,12 +27,41 @@ impl Behave for SpaceNode { } /// # Line Break -/// A line break. +/// Inserts a line break. +/// +/// Advances the paragraph to the next line. A single trailing linebreak at the +/// end of a paragraph is ignored, but more than one creates additional empty +/// lines. +/// +/// ## Example +/// ``` +/// *Date:* 26.12.2022 \ +/// *Topic:* Infrastructure Test \ +/// *Severity:* High \ +/// ``` +/// +/// ## Syntax +/// This function also has dedicated syntax: To insert a linebreak, simply write +/// 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 +/// linebreaks in this paragraph #jb +/// for an _interesting_ result. #jb +/// ``` +/// /// ## Category /// text #[func] @@ -48,6 +77,13 @@ impl LinebreakNode { let justify = args.named("justify")?.unwrap_or(false); Ok(Self { justify }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "justify" => Some(Value::Bool(self.justify)), + _ => None, + } + } } impl Behave for LinebreakNode { @@ -59,6 +95,23 @@ impl Behave for LinebreakNode { /// # Strong Emphasis /// Strongly emphasizes content by increasing the font weight. /// +/// Increases the current font weight by a given `delta`. +/// +/// ## Example +/// ``` +/// This is *strong.* \ +/// This is #strong[too.] \ +/// +/// #show strong: set text(red) +/// And this is *evermore.* +/// ``` +/// +/// ## Syntax +/// This function also has dedicated syntax: To strongly emphasize content, +/// simply enclose it in stars/asterisks (`*`). Note that this only works at +/// 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. @@ -73,6 +126,12 @@ pub struct StrongNode(pub 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 { @@ -111,7 +170,29 @@ impl Fold for Delta { } /// # Emphasis -/// Emphasizes content by flipping the italicness. +/// Emphasizes content by setting it in italics. +/// +/// - If the current [text](@text) style is `{"normal"}`, +/// this turns it into `{"italic"}`. +/// - If it is already `{"italic"}` or `{"oblique"}`, +/// it turns it back to `{"normal"}`. +/// +/// ## Example +/// ``` +/// This is _emphasized._ \ +/// This is #emph[too.] +/// +/// #show emph: it => { +/// text(blue, it.body) +/// } +/// +/// This is _emphasized_ differently. +/// ``` +/// +/// ## Syntax +/// This function also has dedicated syntax: To emphasize content, simply +/// 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) @@ -159,6 +240,13 @@ impl Fold for Toggle { /// # Lowercase /// Convert text or content to lowercase. /// +/// ## Example +/// ``` +/// #lower("ABC") \ +/// #lower[*My Text*] \ +/// #lower[already low] +/// ``` +/// /// ## Parameters /// - text: ToCase (positional, required) /// The text to convert to lowercase. @@ -173,6 +261,13 @@ pub fn lower(args: &mut Args) -> SourceResult { /// # Uppercase /// Convert text or content to uppercase. /// +/// ## Example +/// ``` +/// #upper("abc") \ +/// #upper[*my text*] \ +/// #upper[ALREADY HIGH] +/// ``` +/// /// ## Parameters /// - text: ToCase (positional, required) /// The text to convert to uppercase. @@ -225,6 +320,29 @@ impl Case { /// # Small Capitals /// Display text in small capitals. /// +/// _Note:_ This enables the OpenType `smcp` feature for the font. Not all fonts +/// support this feature (including Typst's current default font, +/// unfortunately). Sometimes smallcaps are part of a dedicated font and +/// sometimes they are not available at all. In the future, this function will +/// support selecting a dedicated smallcaps font as well as synthesizing +/// smallcaps from normal letters, but this is not yet implemented. +/// +/// ## Example +/// ``` +/// #set par(justify: true) +/// #set text(family: "Noto Serif") +/// #set heading(numbering: "I.") +/// +/// #show heading: it => { +/// set block(below: 10pt) +/// set text(weight: "regular") +/// align(center, smallcaps(it)) +/// } +/// +/// = Introduction +/// #lorem(40) +/// ``` +/// /// ## Parameters /// - text: Content (positional, required) /// The text to display to small capitals. diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index e962685d2..191b1fb83 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -26,14 +26,45 @@ use crate::layout::ParNode; use crate::prelude::*; /// # Text -/// Stylable 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 +/// rule is often the simpler choice, calling the text function directly can be +/// useful when passing text as an argument to another function. +/// +/// ## Example +/// ``` +/// #set text(18pt) +/// With a set rule. +/// +/// #emph(text(blue)[ +/// With a function call. +/// ]) +/// ``` /// /// ## Parameters -/// - family: EcoString (positional, variadic, settable) -/// A prioritized sequence of font families. +/// - family: EcoString (positional, variadic, settable) A prioritized sequence +/// of font families. /// -/// - body: Content (positional, required) -/// Content in which all text is styled according to the other arguments. +/// When processing text, Typst tries all specified font families in order +/// until it finds a font that has the necessary glyphs. In the example below, +/// the font `Inria Serif` is preferred, but since it does not contain Arabic +/// glyphs, the arabic text uses `Noto Sans Arabic` instead. +/// +/// ### Example +/// ``` +/// #set text( +/// "Inria Serif", +/// "Noto Sans Arabic", +/// ) +/// +/// This is Latin. \ +/// هذا عربي. +/// +/// ``` +/// +/// - body: Content (positional, required) Content in which all text is styled +/// according to the other arguments. /// /// ## Category /// text @@ -54,75 +85,383 @@ impl TextNode { /// A prioritized sequence of font families. #[property(skip, referenced)] pub const FAMILY: FallbackList = FallbackList(vec![FontFamily::new("IBM Plex Sans")]); - /// Whether to allow font fallback when the primary font list contains no - /// match. + + /// Whether to allow last resort font fallback when the primary font list + /// contains no match. This lets Typst search through all available fonts + /// for the most similar one that has the necessary glyphs. + /// + /// _Note:_ Currently, there are no warnings when fallback is disabled and + /// no glyphs are found. Instead, your text shows up in the form of "tofus": + /// Small boxes that indicate the lack of an appropriate glyph. In the + /// future, you will be able to instruct Typst to issue warnings so you know + /// something is up. + /// + /// # Example + /// ``` + /// #set text(family: "Inria Serif") + /// هذا عربي + /// + /// #set text(fallback: false) + /// هذا عربي + /// ``` pub const FALLBACK: bool = true; - /// How the font is styled. + /// The desired font style. + /// + /// When an italic style is requested and only an oblique one is available, + /// it is used. Similarly, the other way around, an italic style can stand + /// in for an oblique one. When neither an italic nor an oblique style is + /// available, Typst selects the normal style. Since most fonts are only + /// available either in an italic or oblique style, the difference between + /// italic and oblique style is rarely observable. + /// + /// If you want to emphasize your text, you should do so using the + /// [emph](@emph) function instead. This makes it easy to adapt the style + /// later if you change your mind about how to signify the emphasis. + /// + /// # Example + /// ``` + /// #text("IBM Plex Sans", style: "italic")[Italic] + /// #text("DejaVu Sans", style: "oblique")[Oblique] + /// ``` pub const STYLE: FontStyle = FontStyle::Normal; - /// The boldness / thickness of the font's glyphs. + + /// The desired thickness of the font's glyphs. Accepts an integer between + /// `{100}` and `{900}` or one of the predefined weight names. When the + /// desired weight is not available, Typst selects the font from the family + /// that is closest in weight. + /// + /// If you want to strongly emphasize your text, you should do so using the + /// [strong](@strong) function instead. This makes it easy to adapt the + /// style later if you change your mind about how to signify the strong + /// emphasis. + /// + /// # Example + /// ``` + /// #text(weight: "light")[Light] \ + /// #text(weight: "regular")[Regular] \ + /// #text(weight: "medium")[Medium] \ + /// #text(weight: "bold")[Bold] + /// ``` pub const WEIGHT: FontWeight = FontWeight::REGULAR; - /// The width of the glyphs. + + /// The desired width of the glyphs. Accepts a ratio between `{50%}` and + /// `{200%}`. When the desired weight is not available, Typst selects the + /// font from the family that is closest in stretch. + /// + /// # Example + /// ``` + /// #text(stretch: 75%)[Condensed] \ + /// #text(stretch: 100%)[Normal] + /// ``` pub const STRETCH: FontStretch = FontStretch::NORMAL; - /// The size of the glyphs. + /// The size of the glyphs. This value forms the basis of the `em` unit: + /// `{1em}` is equivalent to the font size. + /// + /// You can also give the font size itself in `em` units. Then, it is + /// relative to the previous font size. + /// + /// # Example + /// ``` + /// #set text(size: 20pt) + /// very #text(1.5em)[big] text + /// ``` #[property(shorthand, fold)] pub const SIZE: TextSize = Abs::pt(11.0); + /// The glyph fill color. + /// + /// # Example + /// ``` + /// #set text(fill: red) + /// This text is red. + /// ``` #[property(shorthand)] pub const FILL: Paint = Color::BLACK.into(); + /// The amount of space that should be added between characters. + /// + /// # Example + /// ``` + /// #set text(tracking: 1.5pt) + /// Distant text. + /// ``` #[property(resolve)] pub const TRACKING: Length = Length::zero(); - /// The width of spaces relative to the font's space width. + + /// The amount of space between words. + /// + /// Can be given as an absolute length, but also relative to the width of + /// the space character in the font. + /// + /// # Example + /// ``` + /// #set text(spacing: 200%) + /// Text with distant words. + /// ``` #[property(resolve)] pub const SPACING: Rel = Rel::one(); - /// The offset of the baseline. + + /// An amount to shift the text baseline by. + /// + /// # Example + /// ``` + /// A #text(baseline: 3pt)[lowered] + /// word. + /// ``` #[property(resolve)] pub const BASELINE: Length = Length::zero(); - /// Whether certain glyphs can hang over into the margin. + + /// Whether certain glyphs can hang over into the margin in justified text. + /// This can make justification visually more pleasing. + /// + /// # Example + /// ``` + /// #set par(justify: true) + /// In this particular text, the + /// justification produces a hyphen + /// in the first line. Letting this + /// hyphen hang slightly into the + /// margin makes for a clear + /// paragraph edge. + /// + /// #set text(overhang: false) + /// In this particular text, the + /// justification produces a hyphen + /// in the first line. This time the + /// hyphen does not hang into the + /// margin, making the paragraph's + /// edge less clear. + /// ``` pub const OVERHANG: bool = true; - /// The top end of the text bounding box. + + /// The top end of the conceptual frame around the text used for layout and + /// positioning. This affects the size of containers that hold text. + /// + /// # Example + /// ``` + /// #set text(size: 20pt) + /// #set text(top-edge: "ascender") + /// #rect(fill: aqua)[Typst] + /// + /// #set text(top-edge: "cap-height") + /// #rect(fill: aqua)[Typst] + /// ``` pub const TOP_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::CapHeight); - /// The bottom end of the text bounding box. + + /// The bottom end of the conceptual frame around the text used for layout + /// and positioning. This affects the size of containers that hold text. + /// + /// # Example + /// ``` + /// #set text(size: 20pt) + /// #set text(bottom-edge: "baseline") + /// #rect(fill: aqua)[Typst] + /// + /// #set text(bottom-edge: "descender") + /// #rect(fill: aqua)[Typst] + /// ``` pub const BOTTOM_EDGE: TextEdge = TextEdge::Metric(VerticalFontMetric::Baseline); - /// An ISO 639-1/2/3 language code. + /// An [ISO 639-1/2/3 language code.](https://en.wikipedia.org/wiki/ISO_639) + /// + /// Setting the correct language affects various parts of Typst: + /// + /// - The text processing pipeline can make more informed choices. + /// - Hyphenation will use the correct patterns for the language. + /// - [Smart quotes](@smartquote) turns into the correct quotes for the + /// language. + /// - And all other things which are language-aware. + /// + /// # Example + /// ``` + /// #set text(lang: "de") + /// #outline() + /// + /// = Einleitung + /// In diesem Dokument, ... + /// ``` pub const LANG: Lang = Lang::ENGLISH; - /// An ISO 3166-1 alpha-2 region code. + + /// 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 = None; - /// The direction for text and inline objects. When `auto`, the direction is - /// automatically inferred from the language. + + /// The dominant direction for text and inline objects. Possible values are: + /// + /// - `{auto}`: Automatically infer the direction from the `lang` property. + /// - `{ltr}`: Layout text from left to right. + /// - `{rtl}`: Layout text from right to left. + /// + /// When writing in right-to-left scripts like Arabic or Hebrew, you should + /// set the language or direction. While individual runs of text are + /// automatically layouted in the correct direction, setting the dominant + /// direction gives the bidirectional reordering algorithm the necessary + /// information to correctly place punctuation and inline objects. + /// Furthermore, setting the direction affects the alignment values `start` + /// and `end`, which are equivalent to `left` and `right` in `ltr` text and + /// the other way around in `rtl` text. + /// + /// If you set this to `rtl` and experience bugs or in some way bad looking + /// output, please do get in touch with us through the [contact + /// form](/contact) or our [Discord server](/docs/community/#discord)! + /// + /// # Example + /// ``` + /// #set text(dir: rtl) + /// هذا عربي. + /// ``` #[property(resolve)] pub const DIR: HorizontalDir = HorizontalDir(Smart::Auto); - /// Whether to hyphenate text to improve line breaking. When `auto`, words - /// will will be hyphenated if and only if justification is enabled. + + /// Whether to hyphenate text to improve line breaking. When `{auto}`, text + /// will be hyphenated if and only if justification is enabled. + /// + /// # Example + /// ``` + /// #set par(justify: true) + /// This text illustrates how + /// enabling hyphenation can + /// improve justification. + /// + /// #set text(hyphenate: false) + /// This text illustrates how + /// enabling hyphenation can + /// improve justification. + /// ``` #[property(resolve)] pub const HYPHENATE: Hyphenate = Hyphenate(Smart::Auto); - /// Whether to apply smart quotes. - pub const SMART_QUOTES: bool = true; - /// Whether to apply kerning ("kern"). + /// Whether to apply kerning. + /// + /// When enabled, specific letter pairings move closer together or further + /// apart for a more visually pleasing result. The example below + /// demonstrates how decreasing the gap between the "T" and "o" results in a + /// more natural look. Setting this to `{false}` disables kerning by turning + /// off the OpenType `kern` font feature. + /// + /// # Example + /// ``` + /// #set text(size: 25pt) + /// Totally + /// + /// #set text(kerning: false) + /// Totally + /// ``` pub const KERNING: bool = true; - /// Whether to apply stylistic alternates. ("salt") + + /// Whether to apply stylistic alternates. + /// + /// Sometimes fonts contain alternative glyphs for the same codepoint. + /// Setting this to `{true}` switches to these by enabling the OpenType + /// `salt` font feature. + /// + /// # Example + /// ``` + /// #set text(size: 20pt) + /// 0, a, g, ß + /// + /// #set text(alternates: true) + /// 0, a, g, ß + /// ``` pub const ALTERNATES: bool = false; - /// Which stylistic set to apply. ("ss01" - "ss20") + + /// 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 = None; - /// Whether standard ligatures are active. ("liga", "clig") + + /// Whether standard ligatures are active. + /// + /// Certain letter combinations like "fi" are often displayed as a single + /// merged glyph called a _ligature._ Setting this to `{false}` disables + /// these ligatures by turning off the OpenType `liga` and `clig` font + /// features. + /// + /// # Example + /// ``` + /// #set text(size: 20pt) + /// A fine ligature. + /// + /// #set text(ligatures: false) + /// A fine ligature. + /// ``` pub const LIGATURES: bool = true; - /// Whether ligatures that should be used sparingly are active. ("dlig") + + /// 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; - /// Whether historical ligatures are active. ("hlig") + + /// Whether historical ligatures are active. Setting this to `{true}` + /// enables the OpenType `hlig` font feature. pub const HISTORICAL_LIGATURES: bool = false; - /// Which kind of numbers / figures to select. + + /// Which kind of numbers / figures to select. When set to `{auto}`, the + /// default numbers for the font are used. + /// + /// # Example + /// ``` + /// #set text(20pt, "Noto Sans") + /// #set text(number-type: "lining") + /// Number 9. + /// + /// #set text(number-type: "old-style") + /// Number 9. + /// ``` pub const NUMBER_TYPE: Smart = Smart::Auto; - /// The width of numbers / figures. + + /// The width of numbers / figures. When set to `{auto}`, the default + /// numbers for the font are used. + /// + /// # Example + /// ``` + /// #set text(20pt, "Noto Sans") + /// #set text(number-width: "proportional") + /// A 12 B 34. \ + /// A 56 B 78. + /// + /// #set text(number-width: "tabular") + /// A 12 B 34. \ + /// A 56 B 78. + /// ``` pub const NUMBER_WIDTH: Smart = Smart::Auto; - /// Whether to have a slash through the zero glyph. ("zero") + + /// Whether to have a slash through the zero glyph. Setting this to `{true}` + /// enables the OpenType `zero` font feature. + /// + /// # Example + /// ``` + /// 0, #text(slashed-zero: true)[0] + /// ``` pub const SLASHED_ZERO: bool = false; - /// Whether to convert fractions. ("frac") + + /// Whether to turns numbers into fractions. Setting this to `{true}` + /// enables the OpenType `frac` font feature. + /// + /// # Example + /// ``` + /// 1/2 \ + /// #text(fractions: true)[1/2] + /// ``` pub const FRACTIONS: bool = false; + /// Raw OpenType features to apply. + /// + /// - If given an array of strings, sets the features identified by the + /// strings to `{1}`. + /// - If given a dictionary mapping to numbers, sets the features + /// identified by the keys to the values. + /// + /// # Example + /// ``` + /// // Enable the `frac` feature manually. + /// #set text(features: ("frac",)) + /// 1/2 + /// ``` #[property(fold)] pub const FEATURES: FontFeatures = FontFeatures(vec![]); @@ -272,7 +611,7 @@ impl TextEdge { castable! { TextEdge, v: Length => Self::Length(v), - /// The distance from the baseline to the ascender. + /// 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), @@ -280,7 +619,7 @@ castable! { "x-height" => Self::Metric(VerticalFontMetric::XHeight), /// The baseline on which the letters rest. "baseline" => Self::Metric(VerticalFontMetric::Baseline), - /// The distance from the baseline to the descender. + /// The font's ascender, which typically exceeds the depth of all glyphs. "descender" => Self::Metric(VerticalFontMetric::Descender), } @@ -364,9 +703,11 @@ pub enum NumberType { castable! { NumberType, - /// Numbers that fit well with capital text. + /// 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. + /// Numbers that fit well into a flow of upper- and lowercase text (the + /// OpenType `onum` font feature). "old-style" => Self::OldStyle, } @@ -381,9 +722,9 @@ pub enum NumberWidth { castable! { NumberWidth, - /// Number widths are glyph specific. + /// Numbers with glyph-specific widths (the OpenType `pnum` font feature). "proportional" => Self::Proportional, - /// All numbers are of equal width / monospaced. + /// Numbers of equal width (the OpenType `tnum` font feature). "tabular" => Self::Tabular, } diff --git a/library/src/text/quotes.rs b/library/src/text/quotes.rs index 5965df561..40a9bb82b 100644 --- a/library/src/text/quotes.rs +++ b/library/src/text/quotes.rs @@ -3,11 +3,29 @@ use typst::syntax::is_newline; use crate::prelude::*; /// # Smart Quote -/// A smart quote. +/// A language-aware quote that reacts to its context. +/// +/// Automatically turns into an appropriate opening or closing quote based on +/// the active [text](@text) language. +/// +/// ## Syntax +/// This function also has dedicated syntax: The normal quote characters +/// (`'` and `"`). Typst automatically makes your quotes smart. +/// +/// ## Example +/// ``` +/// "This is in quotes." +/// +/// #set text(lang: "de") +/// "Das ist in Anführungszeichen." +/// +/// #set text(lang: "fr") +/// "C'est entre guillemets." +/// ``` /// /// ## Parameters /// - double: bool (named) -/// Whether to produce a smart double quote. +/// Whether this should be a double quote. /// /// ## Category /// text @@ -20,10 +38,30 @@ pub struct SmartQuoteNode { #[node] impl SmartQuoteNode { + /// Whether smart quotes are enabled. + /// + /// To disable smartness for a single quote, you can also escape it with a + /// backslash. + /// + /// # Example + /// ``` + /// #set smartquote(enabled: false) + /// + /// These are "dumb" quotes. + /// ``` + pub const ENABLED: bool = true; + fn construct(_: &Vm, args: &mut Args) -> SourceResult { let double = args.named("double")?.unwrap_or(true); Ok(Self { double }.pack()) } + + fn field(&self, name: &str) -> Option { + match name { + "double" => Some(Value::Bool(self.double)), + _ => None, + } + } } /// State machine for smart quote subtitution. diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 240f82e8a..938224d04 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -2,24 +2,93 @@ use once_cell::sync::Lazy; use syntect::highlighting as synt; use typst::syntax::{self, LinkedNode}; -use super::{FontFamily, Hyphenate, LinebreakNode, TextNode}; +use super::{FontFamily, Hyphenate, LinebreakNode, SmartQuoteNode, TextNode}; use crate::layout::BlockNode; use crate::prelude::*; -/// # Raw Text +/// # Raw Text / Code /// Raw text with optional syntax highlighting. /// +/// Displays the text verbatim and in a monospace font. This is typically used +/// to embed computer code into your document. +/// +/// ## Syntax +/// This function also has dedicated syntax. You can enclose text in 1 or 3+ +/// backticks (`` ` ``) to make it raw. Two backticks produce empty raw text. +/// When you use three or more backticks, you can additionally specify a +/// language tag for syntax highlighting directly after the opening backticks. +/// Within raw blocks, everything is rendered as is, in particular, there are no +/// escape sequences. +/// +/// ## Example +/// ```` +/// Adding `rbx` to `rcx` gives +/// the desired result. +/// +/// ```rust +/// fn main() { +/// println!("Hello World!"); +/// } +/// ``` +/// ```` +/// /// ## 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): rect.with( +/// fill: luma(240), +/// inset: (x: 3pt), +/// outset: (y: 3pt), +/// radius: 2pt, +/// ) +/// +/// // Display block code in a larger box +/// // with more padding. +/// #show raw.where(block: true): rect.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(Show)] +#[capable(Show, Prepare)] #[derive(Debug, Hash)] pub struct RawNode { /// The raw text. @@ -31,6 +100,17 @@ pub struct RawNode { #[node] impl RawNode { /// The language to syntax-highlight in. + /// + /// Apart from typical language tags known from Markdown, this supports the + /// `{"typ"}` and `{"typc"}` tags for Typst markup and Typst code, + /// respectively. + /// + /// # Example + /// ```` + /// ```typ + /// This is *Typst!* + /// ``` + /// ```` #[property(referenced)] pub const LANG: Option = None; @@ -121,7 +201,7 @@ impl Show for RawNode { let mut map = StyleMap::new(); map.set(TextNode::OVERHANG, false); map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false))); - map.set(TextNode::SMART_QUOTES, false); + map.set(SmartQuoteNode::ENABLED, false); map.set_family(FontFamily::new("IBM Plex Mono"), styles); Ok(realized.styled_with_map(map)) diff --git a/library/src/text/symbol.rs b/library/src/text/symbol.rs index ec2653dff..a59461a7a 100644 --- a/library/src/text/symbol.rs +++ b/library/src/text/symbol.rs @@ -4,9 +4,49 @@ use crate::text::TextNode; /// # Symbol /// A symbol identified by symmie notation. /// +/// Symmie is Typst's notation for Unicode symbols. It is based on the idea of +/// _modifiers._ Many symbols in Unicode are very similar. In symmie, such +/// groups of symbols share a common name. To distinguish between the symbols +/// within a group, we use one or multiple modifiers that are separated from the +/// name by colons. +/// +/// There is currently no easily viewable list of all names, but in the +/// meantime you can rely on the autocompletion in Typst's web editor. +/// +/// ## Syntax +/// This function also has dedicated syntax: In markup, you can enclose symmie +/// notation within colons to produce a symbol. And in math, you can just write +/// the notation directly. There, all letter sequence of length at least two are +/// automatically parsed as symbols (unless a variable of that name is defined). +/// +/// ## Example +/// ``` +/// // In text, with colons. +/// :arrow:l: \ +/// :arrow:r: \ +/// :arrow:t: \ +/// :turtle: \ +/// :face:halo: \ +/// :woman:old: +/// +/// // In math, directly. +/// $f : NN -> RR$ \ +/// $A sub:eq B without C$ \ +/// $a times:div b eq:not c$ +/// ``` +/// /// ## Parameters /// - notation: EcoString (positional, required) -/// The symbols symmie notation. +/// The symbol's symmie notation. +/// +/// Consists of a name, followed by a number colon-separated modifiers +/// in no particular order. +/// +/// ### Example +/// ``` +/// #symbol("NN") \ +/// #symbol("face:grin") +/// ``` /// /// ## Category /// text diff --git a/library/src/visualize/shape.rs b/library/src/visualize/shape.rs index 5edab70b9..d1ced9cdf 100644 --- a/library/src/visualize/shape.rs +++ b/library/src/visualize/shape.rs @@ -94,6 +94,16 @@ impl ShapeNode { styles.set_opt(Self::RADIUS, args.named("radius")?); } } + + fn field(&self, name: &str) -> Option { + match name { + "body" => match &self.0 { + Some(body) => Some(Value::Content(body.clone())), + None => Some(Value::None), + }, + _ => None, + } + } } impl Layout for ShapeNode { diff --git a/macros/src/func.rs b/macros/src/func.rs index 73522f0e4..1f6bb626e 100644 --- a/macros/src/func.rs +++ b/macros/src/func.rs @@ -109,15 +109,13 @@ pub fn section(docs: &mut String, title: &str, level: usize) -> Option { /// Parse the example section. pub fn example(docs: &mut String, level: usize) -> Option { - Some( - section(docs, "Example", level)? - .lines() - .skip_while(|line| !line.contains("```")) - .skip(1) - .take_while(|line| !line.contains("```")) - .collect::>() - .join("\n"), - ) + let section = section(docs, "Example", level)?; + let mut s = unscanny::Scanner::new(§ion); + let count = s.eat_while('`').len(); + let term = "`".repeat(count); + let text = s.eat_until(term.as_str()).trim(); + s.expect(term.as_str()); + Some(text.into()) } /// Parse the parameter section. diff --git a/src/font/mod.rs b/src/font/mod.rs index 13189b6d0..0422f3b9c 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -254,7 +254,7 @@ pub struct LineMetrics { /// Identifies a vertical metric of a font. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum VerticalFontMetric { - /// The distance from the baseline to the typographic ascender. + /// The typographic ascender. /// /// Corresponds to the typographic ascender from the `OS/2` table if present /// and falls back to the ascender from the `hhea` table otherwise. @@ -265,7 +265,7 @@ pub enum VerticalFontMetric { XHeight, /// The baseline on which the letters rest. Baseline, - /// The distance from the baseline to the typographic descender. + /// The typographic descender. /// /// Corresponds to the typographic descender from the `OS/2` table if /// present and falls back to the descender from the `hhea` table otherwise. diff --git a/src/font/variant.rs b/src/font/variant.rs index 085f2ae75..aa9ff1418 100644 --- a/src/font/variant.rs +++ b/src/font/variant.rs @@ -32,11 +32,11 @@ impl Debug for FontVariant { #[derive(Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum FontStyle { - /// The default style. + /// The default, typically upright style. Normal, - /// A cursive style. + /// A cursive style with custom letterform. Italic, - /// A slanted style. + /// Just a slanted version of the normal style. Oblique, } diff --git a/src/model/cast.rs b/src/model/cast.rs index 5c95fe4b3..1a5cae456 100644 --- a/src/model/cast.rs +++ b/src/model/cast.rs @@ -293,11 +293,11 @@ castable! { castable! { FontStyle, - /// The default style. + /// The default, typically upright style. "normal" => Self::Normal, - /// A cursive style. + /// A cursive style with custom letterform. "italic" => Self::Italic, - /// A slanted style. + /// Just a slanted version of the normal style. "oblique" => Self::Oblique, } diff --git a/tests/typ/layout/spacing.typ b/tests/typ/layout/spacing.typ index 3cb1a20ca..fbc5b0d8c 100644 --- a/tests/typ/layout/spacing.typ +++ b/tests/typ/layout/spacing.typ @@ -32,5 +32,5 @@ A #h(1fr) B --- // Missing spacing. -// Error: 11-13 missing argument: spacing +// Error: 11-13 missing argument: amount Totally #h() ignored diff --git a/tests/typ/text/quotes.typ b/tests/typ/text/quotes.typ index f229fd238..0c64867af 100644 --- a/tests/typ/text/quotes.typ +++ b/tests/typ/text/quotes.typ @@ -43,11 +43,11 @@ The 5\'11\" 'quick\' brown fox jumps over the \"lazy" dog\'s ear. // Test turning smart quotes off. He's told some books contain questionable "example text". -#set text(smart-quotes: false) +#set smartquote(enabled: false) He's told some books contain questionable "example text". --- // Test changing properties within text. "She suddenly started speaking french: #text(lang: "fr")['Je suis une banane.']" Roman told me. -Some people's thought on this would be #text(smart-quotes: false)["strange."] +Some people's thought on this would be [#set smartquote(enabled: false); "strange."]