use std::fmt::{self, Debug, Formatter}; use typst_utils::singleton; use crate::diag::{bail, SourceResult}; use crate::engine::Engine; use crate::foundations::{ elem, scope, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, StyleVec, Unlabellable, }; use crate::introspection::{Count, CounterUpdate, Locatable}; use crate::layout::{Em, HAlignment, Length, OuterHAlignment}; use crate::model::Numbering; /// Arranges text, spacing and inline-level elements into a paragraph. /// /// Although this function is primarily used in set rules to affect paragraph /// properties, it can also be used to explicitly render its argument onto a /// paragraph of its own. /// /// # 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", Debug, Construct)] 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. #[resolve] #[ghost] #[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. #[resolve] #[ghost] #[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). #[ghost] #[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. /// ``` #[ghost] pub linebreaks: Smart, /// The indent the first line of a paragraph should have. /// /// Only the first line of a consecutive paragraph will be indented (not /// the first one in a block or on the page). /// /// By typographic convention, paragraph breaks are indicated either by some /// space between paragraphs or by indented first lines. Consider reducing /// the [paragraph spacing]($block.spacing) to the [`leading`]($par.leading) /// when using this property (e.g. using `[#set par(spacing: 0.65em)]`). #[ghost] pub first_line_indent: Length, /// The indent all but the first line of a paragraph should have. #[ghost] #[resolve] pub hanging_indent: Length, /// The contents of the paragraph. #[external] #[required] pub body: Content, /// The paragraph's children. #[internal] #[variadic] pub children: StyleVec, } #[scope] impl ParElem { #[elem] type ParLine; } impl Construct for ParElem { fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult { // The paragraph constructor is special: It doesn't create a paragraph // element. Instead, it just ensures that the passed content lives in a // separate paragraph and styles it. let styles = Self::set(engine, args)?; let body = args.expect::("body")?; Ok(Content::sequence([ ParbreakElem::shared().clone(), body.styled_with_map(styles), ParbreakElem::shared().clone(), ])) } } impl Debug for ParElem { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Par ")?; self.children.fmt(f) } } /// 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, } /// 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 } }