typst_library/model/
par.rs

1use ecow::eco_format;
2use typst_utils::singleton;
3
4use crate::diag::{HintedStrResult, SourceResult, StrResult, bail};
5use crate::engine::Engine;
6use crate::foundations::{
7    AlternativeFold, Args, Cast, CastInfo, Construct, Content, Dict, Fold, FromValue,
8    IntoValue, NativeElement, Packed, Reflect, Smart, Unlabellable, Value, cast, dict,
9    elem, scope,
10};
11use crate::introspection::{Count, CounterUpdate, Locatable, Tagged, Unqueriable};
12use crate::layout::{Abs, Em, HAlignment, Length, OuterHAlignment, Ratio, Rel};
13use crate::model::Numbering;
14
15/// A logical subdivison of textual content.
16///
17/// Typst automatically collects _inline-level_ elements into paragraphs.
18/// Inline-level elements include [text], [horizontal spacing]($h),
19/// [boxes]($box), and [inline equations]($math.equation).
20///
21/// To separate paragraphs, use a blank line (or an explicit [`parbreak`]).
22/// Paragraphs are also automatically interrupted by any block-level element
23/// (like [`block`], [`place`], or anything that shows itself as one of these).
24///
25/// The `par` element is primarily used in set rules to affect paragraph
26/// properties, but it can also be used to explicitly display its argument as a
27/// paragraph of its own. Then, the paragraph's body may not contain any
28/// block-level content.
29///
30/// # Boxes and blocks
31/// As explained above, usually paragraphs only contain inline-level content.
32/// However, you can integrate any kind of block-level content into a paragraph
33/// by wrapping it in a [`box`].
34///
35/// Conversely, you can separate inline-level content from a paragraph by
36/// wrapping it in a [`block`]. In this case, it will not become part of any
37/// paragraph at all. Read the following section for an explanation of why that
38/// matters and how it differs from just adding paragraph breaks around the
39/// content.
40///
41/// # What becomes a paragraph?
42/// When you add inline-level content to your document, Typst will automatically
43/// wrap it in paragraphs. However, a typical document also contains some text
44/// that is not semantically part of a paragraph, for example in a heading or
45/// caption.
46///
47/// The rules for when Typst wraps inline-level content in a paragraph are as
48/// follows:
49///
50/// - All text at the root of a document is wrapped in paragraphs.
51///
52/// - Text in a container (like a `block`) is only wrapped in a paragraph if the
53///   container holds any block-level content. If all of the contents are
54///   inline-level, no paragraph is created.
55///
56/// In the laid-out document, it's not immediately visible whether text became
57/// part of a paragraph. However, it is still important for various reasons:
58///
59/// - Certain paragraph styling like `first-line-indent` will only apply to
60///   proper paragraphs, not any text. Similarly, `par` show rules of course
61///   only trigger on paragraphs.
62///
63/// - A proper distinction between paragraphs and other text helps people who
64///   rely on Assistive Technology (AT) (such as screen readers) navigate and
65///   understand the document properly.
66///
67/// - PDF export will generate a `P` tag only for paragraphs.
68/// - HTML export will generate a `<p>` tag only for paragraphs.
69///
70/// When creating custom reusable components, you can and should take charge
71/// over whether Typst creates paragraphs. By wrapping text in a [`block`]
72/// instead of just adding paragraph breaks around it, you can force the absence
73/// of a paragraph. Conversely, by adding a [`parbreak`] after some content in a
74/// container, you can force it to become a paragraph even if it's just one
75/// word. This is, for example, what [non-`tight`]($list.tight) lists do to
76/// force their items to become paragraphs.
77///
78/// # Example
79/// ```example
80/// #set par(
81///   first-line-indent: 1em,
82///   spacing: 0.65em,
83///   justify: true,
84/// )
85///
86/// We proceed by contradiction.
87/// Suppose that there exists a set
88/// of positive integers $a$, $b$, and
89/// $c$ that satisfies the equation
90/// $a^n + b^n = c^n$ for some
91/// integer value of $n > 2$.
92///
93/// Without loss of generality,
94/// let $a$ be the smallest of the
95/// three integers. Then, we ...
96/// ```
97#[elem(scope, title = "Paragraph", Locatable, Tagged)]
98pub struct ParElem {
99    /// The spacing between lines.
100    ///
101    /// Leading defines the spacing between the [bottom edge]($text.bottom-edge)
102    /// of one line and the [top edge]($text.top-edge) of the following line. By
103    /// default, these two properties are up to the font, but they can also be
104    /// configured manually with a text set rule.
105    ///
106    /// By setting top edge, bottom edge, and leading, you can also configure a
107    /// consistent baseline-to-baseline distance. You could, for instance, set
108    /// the leading to `{1em}`, the top-edge to `{0.8em}`, and the bottom-edge
109    /// to `{-0.2em}` to get a baseline gap of exactly `{2em}`. The exact
110    /// distribution of the top- and bottom-edge values affects the bounds of
111    /// the first and last line.
112    ///
113    /// ```preview
114    /// // Color palette
115    /// #let c = (
116    ///   par-line: aqua.transparentize(60%),
117    ///   leading-line: blue,
118    ///   leading-text: blue.darken(20%),
119    ///   spacing-line: orange.mix(red).darken(15%),
120    ///   spacing-text: orange.mix(red).darken(20%),
121    /// )
122    ///
123    /// // A sample text for measuring font metrics.
124    /// #let sample-text = [A]
125    ///
126    /// // Number of lines in each paragraph
127    /// #let n-lines = (4, 4, 2)
128    /// #let annotated-lines = (4, 8)
129    ///
130    /// // The wide margin is for annotations
131    /// #set page(width: 350pt, margin: (x: 20%))
132    ///
133    /// #context {
134    ///   let text-height = measure(sample-text).height
135    ///   let line-height = text-height + par.leading.to-absolute()
136    ///
137    ///   let jumps = n-lines
138    ///     .map(n => ((text-height,) * n).intersperse(par.leading))
139    ///     .intersperse(par.spacing)
140    ///     .flatten()
141    ///
142    ///   place(grid(
143    ///     ..jumps
144    ///       .enumerate()
145    ///       .map(((i, h)) => if calc.even(i) {
146    ///         // Draw a stripe for the line
147    ///         block(height: h, width: 100%, fill: c.par-line)
148    ///       } else {
149    ///         // Put an annotation for the gap
150    ///         let sw(a, b) = if h == par.leading { a } else { b }
151    ///
152    ///         align(end, block(
153    ///           height: h,
154    ///           outset: (right: sw(0.5em, 1em)),
155    ///           stroke: (
156    ///             left: none,
157    ///             rest: 0.5pt + sw(c.leading-line, c.spacing-line),
158    ///           ),
159    ///           if i / 2 <= sw(..annotated-lines) {
160    ///             place(horizon, dx: 1.3em, text(
161    ///               0.8em,
162    ///               sw(c.leading-text, c.spacing-text),
163    ///               sw([leading], [spacing]),
164    ///             ))
165    ///           },
166    ///         ))
167    ///       })
168    ///   ))
169    ///
170    ///   // Mark top and bottom edges
171    ///   place(
172    ///     // pos: top/bottom edge
173    ///     // dy: Δy to the last mark
174    ///     // kind: leading/spacing
175    ///     for (pos, dy, kind) in (
176    ///       (bottom, text-height, "leading"),
177    ///       (top, par.leading, "leading"),
178    ///       (bottom, (n-lines.first() - 1) * line-height - par.leading, "spacing"),
179    ///       (top, par.spacing, "spacing"),
180    ///     ) {
181    ///       v(dy)
182    ///
183    ///       let c-text = c.at(kind + "-text")
184    ///       let c-line = c.at(kind + "-line")
185    ///
186    ///       place(end, box(
187    ///         height: 0pt,
188    ///         grid(
189    ///           columns: 2,
190    ///           column-gutter: 0.2em,
191    ///           align: pos,
192    ///           move(
193    ///             // Compensate optical illusion
194    ///             dy: if pos == top { -0.2em } else { 0.05em },
195    ///             text(0.8em, c-text)[#repr(pos) edge],
196    ///           ),
197    ///           line(length: 1em, stroke: 0.5pt + c-line),
198    ///         ),
199    ///       ))
200    ///     },
201    ///   )
202    /// }
203    ///
204    /// #set par(justify: true)
205    /// #set text(luma(25%), overhang: false)
206    /// #show ". ": it => it + parbreak()
207    /// #lorem(55)
208    /// ```
209    #[default(Em::new(0.65).into())]
210    pub leading: Length,
211
212    /// The spacing between paragraphs.
213    ///
214    /// Just like leading, this defines the spacing between the bottom edge of a
215    /// paragraph's last line and the top edge of the next paragraph's first
216    /// line.
217    ///
218    /// When a paragraph is adjacent to a [`block`] that is not a paragraph,
219    /// that block's [`above`]($block.above) or [`below`]($block.below) property
220    /// takes precedence over the paragraph spacing. Headings, for instance,
221    /// reduce the spacing below them by default for a better look.
222    #[default(Em::new(1.2).into())]
223    pub spacing: Length,
224
225    /// Whether to justify text in its line.
226    ///
227    /// Hyphenation will be enabled for justified paragraphs if the
228    /// [text function's `hyphenate` property]($text.hyphenate) is set to
229    /// `{auto}` and the current language is known.
230    ///
231    /// Note that the current [alignment]($align.alignment) still has an effect
232    /// on the placement of the last line except if it ends with a
233    /// [justified line break]($linebreak.justify).
234    ///
235    /// By default, Typst only changes the spacing between words to achieve
236    /// justification. However, you can also allow it to adjust the spacing
237    /// between individual characters using the
238    /// [`justification-limits` property]($par.justification-limits).
239    #[default(false)]
240    pub justify: bool,
241
242    /// How much the spacing between words and characters may be adjusted during
243    /// justification.
244    ///
245    /// When justifying text, Typst needs to stretch or shrink a line to the
246    /// full width of the measure. To achieve this, by default, it adjusts the
247    /// spacing between words. Additionally, it can also adjust the spacing
248    /// between individual characters. This property allows you to configure
249    /// lower and upper bounds for these adjustments.
250    ///
251    /// The property accepts a dictionary with two entries, `spacing` and
252    /// `tracking`, each containing a dictionary with the keys `min` and `max`.
253    /// The `min` keys define down to which lower bound gaps may be shrunk while
254    /// the `max` keys define up to which upper bound they may be stretched.
255    ///
256    /// - The `spacing` entry defines how much the width of spaces between words
257    ///   may be adjusted. It is closely related to [`text.spacing`] and its
258    ///   `min` and `max` keys accept [relative lengths]($relative), just like
259    ///   the `spacing` property.
260    ///
261    ///   A `min` value of `{100%}` means that spaces should retain their normal
262    ///   size (i.e. not be shrunk), while a value of `{90% - 0.01em}` would
263    ///   indicate that a space can be shrunk to a width of 90% of its normal
264    ///   width minus 0.01× the current font size. Similarly, a `max` value of
265    ///   `{100% + 0.02em}` means that a space's width can be increased by 0.02×
266    ///   the current font size. The ratio part must always be positive. The
267    ///   length part, meanwhile, must not be positive for `min` and not be
268    ///   negative for `max`.
269    ///
270    ///   Note that spaces may still be expanded beyond the `max` value if there
271    ///   is no way to justify the line otherwise. However, other means of
272    ///   justification (e.g. spacing apart characters if the `tracking` entry
273    ///   is configured accordingly) are first used to their maximum.
274    ///
275    /// - The `tracking` entry defines how much the spacing between letters may
276    ///   be adjusted. It is closely related to [`text.tracking`] and its `min`
277    ///   and `max` keys accept [lengths]($length), just like the `tracking`
278    ///   property. Unlike `spacing`, it does not accept relative lengths
279    ///   because the base of the relative length would vary for each character,
280    ///   leading to an uneven visual appearance. The behavior compared to
281    ///   `spacing` is as if the base was `{100%}`.
282    ///
283    ///   Otherwise, the `min` and `max` values work just like for `spacing`. A
284    ///   `max` value of `{0.01em}` means that additional spacing amounting to
285    ///   0.01× of the current font size may be inserted between every pair of
286    ///   characters. Note that this also includes the gaps between spaces and
287    ///   characters, so for spaces the values of `tracking` act in addition to
288    ///   the values for `spacing`.
289    ///
290    /// If you only specify one of `spacing` or `tracking`, the other retains
291    /// its previously set value (or the default if it was not previously set).
292    ///
293    /// If you want to enable character-level justification, a good value for
294    /// the `min` and `max` keys is around `{0.01em}` to `{0.02em}` (negated for
295    /// `min`). Using the same value for both gives a good baseline, but
296    /// tweaking the two values individually may produce more balanced results,
297    /// as demonstrated in the example below. Be careful not to set the bounds
298    /// too wide, as it quickly looks unnatural.
299    ///
300    /// Using character-level justification is an impactful microtypographical
301    /// technique that can improve the appearance of justified text, especially
302    /// in narrow columns. Note though that character-level justification does
303    /// not work with every font or language. For example, cursive fonts connect
304    /// letters. Using character-level justification would lead to jagged
305    /// connections.
306    ///
307    /// ```example:"Character-level justification"
308    /// #let example(name) = columns(2, gutter: 10pt)[
309    ///   #place(top, float: true, scope: "parent", strong(name))
310    /// >>> Anne Christine Bayley (1~June 1934 – 31~December 2024) was an
311    /// >>> English surgeon. She was awarded the Order of the British Empire
312    /// >>> for her research into HIV/AIDS patients in Zambia and for
313    /// >>> documenting the spread of the disease among heterosexual patients in
314    /// >>> Africa. In addition to her clinical work, she was a lecturer and
315    /// >>> head of the surgery department at the University of Zambia School of
316    /// >>> Medicine. In the 1990s, she returned to England, where she was
317    /// >>> ordained as an Anglican priest. She continued to be active in Africa
318    /// >>> throughout her retirement years.
319    /// <<<   /* Text from https://en.wikipedia.org/wiki/Anne_Bayley */
320    /// ]
321    ///
322    /// #set page(width: 440pt, height: 21em, margin: 15pt)
323    /// #set par(justify: true)
324    /// #set text(size: 0.8em)
325    ///
326    /// #grid(
327    ///   columns: (1fr, 1fr),
328    ///   gutter: 20pt,
329    ///   {
330    ///     // These are Typst's default limits.
331    ///     set par(justification-limits: (
332    ///       spacing: (min: 100% * 2 / 3, max: 150%),
333    ///       tracking: (min: 0em, max: 0em),
334    ///     ))
335    ///     example[Word-level justification]
336    ///   },
337    ///   {
338    ///     // These are our custom character-level limits.
339    ///     set par(justification-limits: (
340    ///       tracking: (min: -0.01em, max: 0.02em),
341    ///     ))
342    ///     example[Character-level justification]
343    ///   },
344    /// )
345    /// ```
346    #[fold]
347    pub justification_limits: JustificationLimits,
348
349    /// How to determine line breaks.
350    ///
351    /// When this property is set to `{auto}`, its default value, optimized line
352    /// breaks will be used for justified paragraphs. Enabling optimized line
353    /// breaks for ragged paragraphs may also be worthwhile to improve the
354    /// appearance of the text.
355    ///
356    /// ```example
357    /// #set page(width: 207pt)
358    /// #set par(linebreaks: "simple")
359    /// Some texts feature many longer
360    /// words. Those are often exceedingly
361    /// challenging to break in a visually
362    /// pleasing way.
363    ///
364    /// #set par(linebreaks: "optimized")
365    /// Some texts feature many longer
366    /// words. Those are often exceedingly
367    /// challenging to break in a visually
368    /// pleasing way.
369    /// ```
370    pub linebreaks: Smart<Linebreaks>,
371
372    /// The indent the first line of a paragraph should have.
373    ///
374    /// By default, only the first line of a consecutive paragraph will be
375    /// indented (not the first one in the document or container, and not
376    /// paragraphs immediately following other block-level elements).
377    ///
378    /// If you want to indent all paragraphs instead, you can pass a dictionary
379    /// containing the `amount` of indent as a length and the pair
380    /// `{all: true}`. When `all` is omitted from the dictionary, it defaults to
381    /// `{false}`.
382    ///
383    /// By typographic convention, paragraph breaks are indicated either by some
384    /// space between paragraphs or by indented first lines. Consider
385    /// - reducing the [paragraph `spacing`]($par.spacing) to the
386    ///   [`leading`]($par.leading) using `{set par(spacing: 0.65em)}`
387    /// - increasing the [block `spacing`]($block.spacing) (which inherits the
388    ///   paragraph spacing by default) to the original paragraph spacing using
389    ///   `{set block(spacing: 1.2em)}`
390    ///
391    /// ```example
392    /// #set block(spacing: 1.2em)
393    /// #set par(
394    ///   first-line-indent: 1.5em,
395    ///   spacing: 0.65em,
396    /// )
397    ///
398    /// The first paragraph is not affected
399    /// by the indent.
400    ///
401    /// But the second paragraph is.
402    ///
403    /// #line(length: 100%)
404    ///
405    /// #set par(first-line-indent: (
406    ///   amount: 1.5em,
407    ///   all: true,
408    /// ))
409    ///
410    /// Now all paragraphs are affected
411    /// by the first line indent.
412    ///
413    /// Even the first one.
414    /// ```
415    pub first_line_indent: FirstLineIndent,
416
417    /// The indent that all but the first line of a paragraph should have.
418    ///
419    /// ```example
420    /// #set par(hanging-indent: 1em)
421    ///
422    /// #lorem(15)
423    /// ```
424    pub hanging_indent: Length,
425
426    /// The contents of the paragraph.
427    #[required]
428    pub body: Content,
429}
430
431#[scope]
432impl ParElem {
433    #[elem]
434    type ParLine;
435}
436
437/// Configures how justification may distribute spacing.
438#[derive(Debug, Copy, Clone, PartialEq, Hash)]
439pub struct JustificationLimits {
440    /// Limits for spacing, relative to the space width.
441    spacing: Option<Limits<Rel>>,
442    /// Limits for tracking, _in addition_ to the glyph width.
443    tracking: Option<Limits<Length>>,
444}
445
446impl JustificationLimits {
447    /// Access the spacing limits.
448    pub fn spacing(&self) -> &Limits<Rel> {
449        self.spacing.as_ref().unwrap_or(&Limits::SPACING_DEFAULT)
450    }
451
452    /// Access the tracking limits.
453    pub fn tracking(&self) -> &Limits<Length> {
454        self.tracking.as_ref().unwrap_or(&Limits::TRACKING_DEFAULT)
455    }
456}
457
458cast! {
459    JustificationLimits,
460    self => {
461        let mut dict = Dict::new();
462        if let Some(spacing) = &self.spacing {
463            dict.insert("spacing".into(), spacing.into_value());
464        }
465        if let Some(tracking) = &self.tracking {
466            dict.insert("tracking".into(), tracking.into_value());
467        }
468        Value::Dict(dict)
469    },
470    mut dict: Dict => {
471        let spacing = dict
472            .take("spacing")
473            .ok()
474            .map(|v| Limits::cast(v, "spacing"))
475            .transpose()?;
476        let tracking = dict
477            .take("tracking")
478            .ok()
479            .map(|v| Limits::cast(v, "tracking"))
480            .transpose()?;
481        dict.finish(&["spacing", "tracking"])?;
482        Self { spacing, tracking }
483    },
484}
485
486impl Fold for JustificationLimits {
487    fn fold(self, outer: Self) -> Self {
488        Self {
489            spacing: self.spacing.fold_or(outer.spacing),
490            tracking: self.tracking.fold_or(outer.tracking),
491        }
492    }
493}
494
495impl Default for JustificationLimits {
496    fn default() -> Self {
497        Self {
498            spacing: Some(Limits::SPACING_DEFAULT),
499            tracking: Some(Limits::TRACKING_DEFAULT),
500        }
501    }
502}
503
504/// Determines the minimum and maximum size by or to which spacing may be shrunk
505/// and stretched.
506#[derive(Debug, Copy, Clone, PartialEq, Hash)]
507pub struct Limits<T> {
508    /// Minimum allowable adjustment.
509    pub min: T,
510    /// Maximum allowable adjustment.
511    pub max: T,
512}
513
514impl Limits<Rel> {
515    const SPACING_DEFAULT: Self = Self {
516        min: Rel::new(Ratio::new(2.0 / 3.0), Length::zero()),
517        max: Rel::new(Ratio::new(1.5), Length::zero()),
518    };
519}
520
521impl Limits<Length> {
522    const TRACKING_DEFAULT: Self = Self { min: Length::zero(), max: Length::zero() };
523}
524
525impl<T: Reflect> Reflect for Limits<T> {
526    fn input() -> CastInfo {
527        Dict::input()
528    }
529
530    fn output() -> CastInfo {
531        Dict::output()
532    }
533
534    fn castable(value: &Value) -> bool {
535        Dict::castable(value)
536    }
537}
538
539impl<T: IntoValue> IntoValue for Limits<T> {
540    fn into_value(self) -> Value {
541        Value::Dict(dict! {
542            "min" => self.min,
543            "max" => self.max,
544        })
545    }
546}
547
548impl<T> Limits<T> {
549    /// Not implementing `FromValue` here because we want to pass the `field`
550    /// for the error message. Ideally, the casting infrastructure would be
551    /// bit more flexible here.
552    fn cast(value: Value, field: &str) -> HintedStrResult<Self>
553    where
554        T: FromValue + Limit,
555    {
556        let mut dict: Dict = value.cast()?;
557        let mut take = |key, check: fn(T) -> StrResult<T>| {
558            dict.take(key)?
559                .cast::<T>()
560                .map_err(|hinted| hinted.message().clone())
561                .and_then(check)
562                .map_err(|err| {
563                    eco_format!("`{key}` value of `{field}` is invalid ({err})")
564                })
565        };
566        let min = take("min", Limit::checked_min)?;
567        let max = take("max", Limit::checked_max)?;
568        dict.finish(&["min", "max"])?;
569        Ok(Self { min, max })
570    }
571}
572
573impl<T> Fold for Limits<T> {
574    fn fold(self, _: Self) -> Self {
575        self
576    }
577}
578
579/// Validation for limit components.
580trait Limit: Sized {
581    fn checked_min(self) -> StrResult<Self>;
582    fn checked_max(self) -> StrResult<Self>;
583}
584
585impl Limit for Length {
586    fn checked_min(self) -> StrResult<Self> {
587        if self.abs > Abs::zero() || self.em > Em::zero() {
588            bail!("length must be negative or zero");
589        }
590        Ok(self)
591    }
592
593    fn checked_max(self) -> StrResult<Self> {
594        if self.abs < Abs::zero() || self.em < Em::zero() {
595            bail!("length must be positive or zero");
596        }
597        Ok(self)
598    }
599}
600
601impl Limit for Rel<Length> {
602    fn checked_min(self) -> StrResult<Self> {
603        if self.rel <= Ratio::zero() {
604            bail!("ratio must be positive");
605        }
606        self.abs.checked_min()?;
607        Ok(self)
608    }
609
610    fn checked_max(self) -> StrResult<Self> {
611        if self.rel <= Ratio::zero() {
612            bail!("ratio must be positive");
613        }
614        self.abs.checked_max()?;
615        Ok(self)
616    }
617}
618
619/// How to determine line breaks in a paragraph.
620#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
621pub enum Linebreaks {
622    /// Determine the line breaks in a simple first-fit style.
623    Simple,
624    /// Optimize the line breaks for the whole paragraph.
625    ///
626    /// Typst will try to produce more evenly filled lines of text by
627    /// considering the whole paragraph when calculating line breaks.
628    Optimized,
629}
630
631/// Configuration for first line indent.
632#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
633pub struct FirstLineIndent {
634    /// The amount of indent.
635    pub amount: Length,
636    /// Whether to indent all paragraphs, not just consecutive ones.
637    pub all: bool,
638}
639
640cast! {
641    FirstLineIndent,
642    self => Value::Dict(self.into()),
643    amount: Length => Self { amount, all: false },
644    mut dict: Dict => {
645        let amount = dict.take("amount")?.cast()?;
646        let all = dict.take("all").ok().map(|v| v.cast()).transpose()?.unwrap_or(false);
647        dict.finish(&["amount", "all"])?;
648        Self { amount, all }
649    },
650}
651
652impl From<FirstLineIndent> for Dict {
653    fn from(indent: FirstLineIndent) -> Self {
654        dict! {
655            "amount" => indent.amount,
656            "all" => indent.all,
657        }
658    }
659}
660
661/// A paragraph break.
662///
663/// This starts a new paragraph. Especially useful when used within code like
664/// [for loops]($scripting/#loops). Multiple consecutive
665/// paragraph breaks collapse into a single one.
666///
667/// # Example
668/// ```example
669/// #for i in range(3) {
670///   [Blind text #i: ]
671///   lorem(5)
672///   parbreak()
673/// }
674/// ```
675///
676/// # Syntax
677/// Instead of calling this function, you can insert a blank line into your
678/// markup to create a paragraph break.
679#[elem(title = "Paragraph Break", Unlabellable)]
680pub struct ParbreakElem {}
681
682impl ParbreakElem {
683    /// Get the globally shared paragraph element.
684    pub fn shared() -> &'static Content {
685        singleton!(Content, ParbreakElem::new().pack())
686    }
687}
688
689impl Unlabellable for Packed<ParbreakElem> {}
690
691/// A paragraph line.
692///
693/// This element is exclusively used for line number configuration through set
694/// rules and cannot be placed.
695///
696/// The [`numbering`]($par.line.numbering) option is used to enable line
697/// numbers by specifying a numbering format.
698///
699/// ```example
700/// >>> #set page(margin: (left: 3em))
701/// #set par.line(numbering: "1")
702///
703/// Roses are red. \
704/// Violets are blue. \
705/// Typst is there for you.
706/// ```
707///
708/// The `numbering` option takes either a predefined
709/// [numbering pattern]($numbering) or a function returning styled content. You
710/// can disable line numbers for text inside certain elements by setting the
711/// numbering to `{none}` using show-set rules.
712///
713/// ```example
714/// >>> #set page(margin: (left: 3em))
715/// // Styled red line numbers.
716/// #set par.line(
717///   numbering: n => text(red)[#n]
718/// )
719///
720/// // Disable numbers inside figures.
721/// #show figure: set par.line(
722///   numbering: none
723/// )
724///
725/// Roses are red. \
726/// Violets are blue.
727///
728/// #figure(
729///   caption: [Without line numbers.]
730/// )[
731///   Lorem ipsum \
732///   dolor sit amet
733/// ]
734///
735/// The text above is a sample \
736/// originating from distant times.
737/// ```
738///
739/// This element exposes further options which may be used to control other
740/// aspects of line numbering, such as its [alignment]($par.line.number-align)
741/// or [margin]($par.line.number-margin). In addition, you can control whether
742/// the numbering is reset on each page through the
743/// [`numbering-scope`]($par.line.numbering-scope) option.
744#[elem(name = "line", title = "Paragraph Line", keywords = ["line numbering"], Construct, Locatable)]
745pub struct ParLine {
746    /// How to number each line. Accepts a
747    /// [numbering pattern or function]($numbering) taking a single number.
748    ///
749    /// ```example
750    /// >>> #set page(margin: (left: 3em))
751    /// #set par.line(numbering: "I")
752    ///
753    /// Roses are red. \
754    /// Violets are blue. \
755    /// Typst is there for you.
756    /// ```
757    ///
758    /// ```example
759    /// >>> #set page(width: 200pt, margin: (left: 3em))
760    /// #set par.line(
761    ///   numbering: i => if calc.rem(i, 5) == 0 or i == 1 { i },
762    /// )
763    ///
764    /// #lorem(60)
765    /// ```
766    #[ghost]
767    pub numbering: Option<Numbering>,
768
769    /// The alignment of line numbers associated with each line.
770    ///
771    /// The default of `{auto}` indicates a smart default where numbers grow
772    /// horizontally away from the text, considering the margin they're in and
773    /// the current text direction.
774    ///
775    /// ```example
776    /// >>> #set page(margin: (left: 3em))
777    /// #set par.line(
778    ///   numbering: "I",
779    ///   number-align: left,
780    /// )
781    ///
782    /// Hello world! \
783    /// Today is a beautiful day \
784    /// For exploring the world.
785    /// ```
786    #[ghost]
787    pub number_align: Smart<HAlignment>,
788
789    /// The margin at which line numbers appear.
790    ///
791    /// _Note:_ In a multi-column document, the line numbers for paragraphs
792    /// inside the last column will always appear on the `{end}` margin (right
793    /// margin for left-to-right text and left margin for right-to-left),
794    /// regardless of this configuration. That behavior cannot be changed at
795    /// this moment.
796    ///
797    /// ```example
798    /// >>> #set page(margin: (right: 3em))
799    /// #set par.line(
800    ///   numbering: "1",
801    ///   number-margin: right,
802    /// )
803    ///
804    /// = Report
805    /// - Brightness: Dark, yet darker
806    /// - Readings: Negative
807    /// ```
808    #[ghost]
809    #[default(OuterHAlignment::Start)]
810    pub number_margin: OuterHAlignment,
811
812    /// The distance between line numbers and text.
813    ///
814    /// The default value of `{auto}` results in a clearance that is adaptive to
815    /// the page width and yields reasonable results in most cases.
816    ///
817    /// ```example
818    /// >>> #set page(margin: (left: 3em))
819    /// #set par.line(
820    ///   numbering: "1",
821    ///   number-clearance: 4pt,
822    /// )
823    ///
824    /// Typesetting \
825    /// Styling \
826    /// Layout
827    /// ```
828    #[ghost]
829    #[default]
830    pub number_clearance: Smart<Length>,
831
832    /// Controls when to reset line numbering.
833    ///
834    /// _Note:_ The line numbering scope must be uniform across each page run (a
835    /// page run is a sequence of pages without an explicit pagebreak in
836    /// between). For this reason, set rules for it should be defined before any
837    /// page content, typically at the very start of the document.
838    ///
839    /// ```example
840    /// >>> #set page(margin: (left: 3em))
841    /// #set par.line(
842    ///   numbering: "1",
843    ///   numbering-scope: "page",
844    /// )
845    ///
846    /// First line \
847    /// Second line
848    /// #pagebreak()
849    /// First line again \
850    /// Second line again
851    /// ```
852    #[ghost]
853    #[default(LineNumberingScope::Document)]
854    pub numbering_scope: LineNumberingScope,
855}
856
857impl Construct for ParLine {
858    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
859        bail!(args.span, "cannot be constructed manually");
860    }
861}
862
863/// Possible line numbering scope options, indicating how often the line number
864/// counter should be reset.
865///
866/// Note that, currently, manually resetting the line number counter is not
867/// supported.
868#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
869pub enum LineNumberingScope {
870    /// Indicates that the line number counter spans the whole document, i.e.,
871    /// it's never automatically reset.
872    Document,
873    /// Indicates that the line number counter should be reset at the start of
874    /// every new page.
875    Page,
876}
877
878/// A marker used to indicate the presence of a line.
879///
880/// This element is added to each line in a paragraph and later searched to
881/// find out where to add line numbers.
882#[elem(Construct, Unqueriable, Locatable, Count)]
883pub struct ParLineMarker {
884    #[internal]
885    #[required]
886    pub numbering: Numbering,
887
888    #[internal]
889    #[required]
890    pub number_align: Smart<HAlignment>,
891
892    #[internal]
893    #[required]
894    pub number_margin: OuterHAlignment,
895
896    #[internal]
897    #[required]
898    pub number_clearance: Smart<Length>,
899}
900
901impl Construct for ParLineMarker {
902    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
903        bail!(args.span, "cannot be constructed manually");
904    }
905}
906
907impl Count for Packed<ParLineMarker> {
908    fn update(&self) -> Option<CounterUpdate> {
909        // The line counter must be updated manually by the root flow.
910        None
911    }
912}