Skip to main content

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