use typst_utils::singleton; use crate::diag::{SourceResult, bail}; use crate::engine::Engine; use crate::foundations::{ Args, Cast, Construct, Content, Dict, NativeElement, Packed, Smart, Unlabellable, Value, cast, dict, elem, scope, }; use crate::introspection::{Count, CounterUpdate, Locatable}; use crate::layout::{Em, HAlignment, Length, OuterHAlignment}; use crate::model::Numbering; /// A logical subdivison of textual content. /// /// Typst automatically collects _inline-level_ elements into paragraphs. /// Inline-level elements include [text], [horizontal spacing]($h), /// [boxes]($box), and [inline equations]($math.equation). /// /// To separate paragraphs, use a blank line (or an explicit [`parbreak`]). /// Paragraphs are also automatically interrupted by any block-level element /// (like [`block`], [`place`], or anything that shows itself as one of these). /// /// The `par` element is primarily used in set rules to affect paragraph /// properties, but it can also be used to explicitly display its argument as a /// paragraph of its own. Then, the paragraph's body may not contain any /// block-level content. /// /// # Boxes and blocks /// As explained above, usually paragraphs only contain inline-level content. /// However, you can integrate any kind of block-level content into a paragraph /// by wrapping it in a [`box`]. /// /// Conversely, you can separate inline-level content from a paragraph by /// wrapping it in a [`block`]. In this case, it will not become part of any /// paragraph at all. Read the following section for an explanation of why that /// matters and how it differs from just adding paragraph breaks around the /// content. /// /// # What becomes a paragraph? /// When you add inline-level content to your document, Typst will automatically /// wrap it in paragraphs. However, a typical document also contains some text /// that is not semantically part of a paragraph, for example in a heading or /// caption. /// /// The rules for when Typst wraps inline-level content in a paragraph are as /// follows: /// /// - All text at the root of a document is wrapped in paragraphs. /// /// - Text in a container (like a `block`) is only wrapped in a paragraph if the /// container holds any block-level content. If all of the contents are /// inline-level, no paragraph is created. /// /// In the laid-out document, it's not immediately visible whether text became /// part of a paragraph. However, it is still important for various reasons: /// /// - Certain paragraph styling like `first-line-indent` will only apply to /// proper paragraphs, not any text. Similarly, `par` show rules of course /// only trigger on paragraphs. /// /// - A proper distinction between paragraphs and other text helps people who /// rely on assistive technologies (such as screen readers) navigate and /// understand the document properly. Currently, this only applies to HTML /// export since Typst does not yet output accessible PDFs, but support for /// this is planned for the near future. /// /// - HTML export will generate a `

` tag only for paragraphs. /// /// When creating custom reusable components, you can and should take charge /// over whether Typst creates paragraphs. By wrapping text in a [`block`] /// instead of just adding paragraph breaks around it, you can force the absence /// of a paragraph. Conversely, by adding a [`parbreak`] after some content in a /// container, you can force it to become a paragraph even if it's just one /// word. This is, for example, what [non-`tight`]($list.tight) lists do to /// force their items to become paragraphs. /// /// # Example /// ```example /// #set par( /// first-line-indent: 1em, /// spacing: 0.65em, /// justify: true, /// ) /// /// We proceed by contradiction. /// Suppose that there exists a set /// of positive integers $a$, $b$, and /// $c$ that satisfies the equation /// $a^n + b^n = c^n$ for some /// integer value of $n > 2$. /// /// Without loss of generality, /// let $a$ be the smallest of the /// three integers. Then, we ... /// ``` #[elem(scope, title = "Paragraph", Locatable)] pub struct ParElem { /// The spacing between lines. /// /// Leading defines the spacing between the [bottom edge]($text.bottom-edge) /// of one line and the [top edge]($text.top-edge) of the following line. By /// default, these two properties are up to the font, but they can also be /// configured manually with a text set rule. /// /// By setting top edge, bottom edge, and leading, you can also configure a /// consistent baseline-to-baseline distance. You could, for instance, set /// the leading to `{1em}`, the top-edge to `{0.8em}`, and the bottom-edge /// to `{-0.2em}` to get a baseline gap of exactly `{2em}`. The exact /// distribution of the top- and bottom-edge values affects the bounds of /// the first and last line. #[default(Em::new(0.65).into())] pub leading: Length, /// The spacing between paragraphs. /// /// Just like leading, this defines the spacing between the bottom edge of a /// paragraph's last line and the top edge of the next paragraph's first /// line. /// /// When a paragraph is adjacent to a [`block`] that is not a paragraph, /// that block's [`above`]($block.above) or [`below`]($block.below) property /// takes precedence over the paragraph spacing. Headings, for instance, /// reduce the spacing below them by default for a better look. #[default(Em::new(1.2).into())] pub spacing: Length, /// Whether to justify text in its line. /// /// Hyphenation will be enabled for justified paragraphs if the /// [text function's `hyphenate` property]($text.hyphenate) is set to /// `{auto}` and the current language is known. /// /// Note that the current [alignment]($align.alignment) still has an effect /// on the placement of the last line except if it ends with a /// [justified line break]($linebreak.justify). #[default(false)] pub justify: bool, /// How to determine line breaks. /// /// When this property is set to `{auto}`, its default value, optimized line /// breaks will be used for justified paragraphs. Enabling optimized line /// breaks for ragged paragraphs may also be worthwhile to improve the /// appearance of the text. /// /// ```example /// #set page(width: 207pt) /// #set par(linebreaks: "simple") /// Some texts feature many longer /// words. Those are often exceedingly /// challenging to break in a visually /// pleasing way. /// /// #set par(linebreaks: "optimized") /// Some texts feature many longer /// words. Those are often exceedingly /// challenging to break in a visually /// pleasing way. /// ``` pub linebreaks: Smart, /// The indent the first line of a paragraph should have. /// /// By default, only the first line of a consecutive paragraph will be /// indented (not the first one in the document or container, and not /// paragraphs immediately following other block-level elements). /// /// If you want to indent all paragraphs instead, you can pass a dictionary /// containing the `amount` of indent as a length and the pair /// `{all: true}`. When `all` is omitted from the dictionary, it defaults to /// `{false}`. /// /// By typographic convention, paragraph breaks are indicated either by some /// space between paragraphs or by indented first lines. Consider /// - reducing the [paragraph `spacing`]($par.spacing) to the /// [`leading`]($par.leading) using `{set par(spacing: 0.65em)}` /// - increasing the [block `spacing`]($block.spacing) (which inherits the /// paragraph spacing by default) to the original paragraph spacing using /// `{set block(spacing: 1.2em)}` /// /// ```example /// #set block(spacing: 1.2em) /// #set par( /// first-line-indent: 1.5em, /// spacing: 0.65em, /// ) /// /// The first paragraph is not affected /// by the indent. /// /// But the second paragraph is. /// /// #line(length: 100%) /// /// #set par(first-line-indent: ( /// amount: 1.5em, /// all: true, /// )) /// /// Now all paragraphs are affected /// by the first line indent. /// /// Even the first one. /// ``` pub first_line_indent: FirstLineIndent, /// The indent that all but the first line of a paragraph should have. /// /// ```example /// #set par(hanging-indent: 1em) /// /// #lorem(15) /// ``` pub hanging_indent: Length, /// The contents of the paragraph. #[required] pub body: Content, } #[scope] impl ParElem { #[elem] type ParLine; } /// How to determine line breaks in a paragraph. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)] pub enum Linebreaks { /// Determine the line breaks in a simple first-fit style. Simple, /// Optimize the line breaks for the whole paragraph. /// /// Typst will try to produce more evenly filled lines of text by /// considering the whole paragraph when calculating line breaks. Optimized, } /// Configuration for first line indent. #[derive(Debug, Default, Copy, Clone, PartialEq, Hash)] pub struct FirstLineIndent { /// The amount of indent. pub amount: Length, /// Whether to indent all paragraphs, not just consecutive ones. pub all: bool, } cast! { FirstLineIndent, self => Value::Dict(self.into()), amount: Length => Self { amount, all: false }, mut dict: Dict => { let amount = dict.take("amount")?.cast()?; let all = dict.take("all").ok().map(|v| v.cast()).transpose()?.unwrap_or(false); dict.finish(&["amount", "all"])?; Self { amount, all } }, } impl From for Dict { fn from(indent: FirstLineIndent) -> Self { dict! { "amount" => indent.amount, "all" => indent.all, } } } /// A paragraph break. /// /// This starts a new paragraph. Especially useful when used within code like /// [for loops]($scripting/#loops). Multiple consecutive /// paragraph breaks collapse into a single one. /// /// # Example /// ```example /// #for i in range(3) { /// [Blind text #i: ] /// lorem(5) /// parbreak() /// } /// ``` /// /// # Syntax /// Instead of calling this function, you can insert a blank line into your /// markup to create a paragraph break. #[elem(title = "Paragraph Break", Unlabellable)] pub struct ParbreakElem {} impl ParbreakElem { /// Get the globally shared paragraph element. pub fn shared() -> &'static Content { singleton!(Content, ParbreakElem::new().pack()) } } impl Unlabellable for Packed {} /// A paragraph line. /// /// This element is exclusively used for line number configuration through set /// rules and cannot be placed. /// /// The [`numbering`]($par.line.numbering) option is used to enable line /// numbers by specifying a numbering format. /// /// ```example /// >>> #set page(margin: (left: 3em)) /// #set par.line(numbering: "1") /// /// Roses are red. \ /// Violets are blue. \ /// Typst is there for you. /// ``` /// /// The `numbering` option takes either a predefined /// [numbering pattern]($numbering) or a function returning styled content. You /// can disable line numbers for text inside certain elements by setting the /// numbering to `{none}` using show-set rules. /// /// ```example /// >>> #set page(margin: (left: 3em)) /// // Styled red line numbers. /// #set par.line( /// numbering: n => text(red)[#n] /// ) /// /// // Disable numbers inside figures. /// #show figure: set par.line( /// numbering: none /// ) /// /// Roses are red. \ /// Violets are blue. /// /// #figure( /// caption: [Without line numbers.] /// )[ /// Lorem ipsum \ /// dolor sit amet /// ] /// /// The text above is a sample \ /// originating from distant times. /// ``` /// /// This element exposes further options which may be used to control other /// aspects of line numbering, such as its [alignment]($par.line.number-align) /// or [margin]($par.line.number-margin). In addition, you can control whether /// the numbering is reset on each page through the /// [`numbering-scope`]($par.line.numbering-scope) option. #[elem(name = "line", title = "Paragraph Line", keywords = ["line numbering"], Construct, Locatable)] pub struct ParLine { /// How to number each line. Accepts a /// [numbering pattern or function]($numbering). /// /// ```example /// >>> #set page(margin: (left: 3em)) /// #set par.line(numbering: "I") /// /// Roses are red. \ /// Violets are blue. \ /// Typst is there for you. /// ``` #[ghost] pub numbering: Option, /// The alignment of line numbers associated with each line. /// /// The default of `{auto}` indicates a smart default where numbers grow /// horizontally away from the text, considering the margin they're in and /// the current text direction. /// /// ```example /// >>> #set page(margin: (left: 3em)) /// #set par.line( /// numbering: "I", /// number-align: left, /// ) /// /// Hello world! \ /// Today is a beautiful day \ /// For exploring the world. /// ``` #[ghost] pub number_align: Smart, /// The margin at which line numbers appear. /// /// _Note:_ In a multi-column document, the line numbers for paragraphs /// inside the last column will always appear on the `{end}` margin (right /// margin for left-to-right text and left margin for right-to-left), /// regardless of this configuration. That behavior cannot be changed at /// this moment. /// /// ```example /// >>> #set page(margin: (right: 3em)) /// #set par.line( /// numbering: "1", /// number-margin: right, /// ) /// /// = Report /// - Brightness: Dark, yet darker /// - Readings: Negative /// ``` #[ghost] #[default(OuterHAlignment::Start)] pub number_margin: OuterHAlignment, /// The distance between line numbers and text. /// /// The default value of `{auto}` results in a clearance that is adaptive to /// the page width and yields reasonable results in most cases. /// /// ```example /// >>> #set page(margin: (left: 3em)) /// #set par.line( /// numbering: "1", /// number-clearance: 4pt, /// ) /// /// Typesetting \ /// Styling \ /// Layout /// ``` #[ghost] #[default] pub number_clearance: Smart, /// Controls when to reset line numbering. /// /// _Note:_ The line numbering scope must be uniform across each page run (a /// page run is a sequence of pages without an explicit pagebreak in /// between). For this reason, set rules for it should be defined before any /// page content, typically at the very start of the document. /// /// ```example /// >>> #set page(margin: (left: 3em)) /// #set par.line( /// numbering: "1", /// numbering-scope: "page", /// ) /// /// First line \ /// Second line /// #pagebreak() /// First line again \ /// Second line again /// ``` #[ghost] #[default(LineNumberingScope::Document)] pub numbering_scope: LineNumberingScope, } impl Construct for ParLine { fn construct(_: &mut Engine, args: &mut Args) -> SourceResult { bail!(args.span, "cannot be constructed manually"); } } /// Possible line numbering scope options, indicating how often the line number /// counter should be reset. /// /// Note that, currently, manually resetting the line number counter is not /// supported. #[derive(Debug, Cast, Clone, Copy, PartialEq, Eq, Hash)] pub enum LineNumberingScope { /// Indicates that the line number counter spans the whole document, i.e., /// it's never automatically reset. Document, /// Indicates that the line number counter should be reset at the start of /// every new page. Page, } /// A marker used to indicate the presence of a line. /// /// This element is added to each line in a paragraph and later searched to /// find out where to add line numbers. #[elem(Construct, Locatable, Count)] pub struct ParLineMarker { #[internal] #[required] pub numbering: Numbering, #[internal] #[required] pub number_align: Smart, #[internal] #[required] pub number_margin: OuterHAlignment, #[internal] #[required] pub number_clearance: Smart, } impl Construct for ParLineMarker { fn construct(_: &mut Engine, args: &mut Args) -> SourceResult { bail!(args.span, "cannot be constructed manually"); } } impl Count for Packed { fn update(&self) -> Option { // The line counter must be updated manually by the root flow. None } }