typst_library/model/
par.rs

1use typst_utils::singleton;
2
3use crate::diag::{bail, SourceResult};
4use crate::engine::Engine;
5use crate::foundations::{
6    cast, dict, elem, scope, Args, Cast, Construct, Content, Dict, NativeElement, Packed,
7    Smart, Unlabellable, Value,
8};
9use crate::introspection::{Count, CounterUpdate, Locatable};
10use crate::layout::{Em, HAlignment, Length, OuterHAlignment};
11use crate::model::Numbering;
12
13/// A logical subdivison of textual content.
14///
15/// Typst automatically collects _inline-level_ elements into paragraphs.
16/// Inline-level elements include [text], [horizontal spacing]($h),
17/// [boxes]($box), and [inline equations]($math.equation).
18///
19/// To separate paragraphs, use a blank line (or an explicit [`parbreak`]).
20/// Paragraphs are also automatically interrupted by any block-level element
21/// (like [`block`], [`place`], or anything that shows itself as one of these).
22///
23/// The `par` element is primarily used in set rules to affect paragraph
24/// properties, but it can also be used to explicitly display its argument as a
25/// paragraph of its own. Then, the paragraph's body may not contain any
26/// block-level content.
27///
28/// # Boxes and blocks
29/// As explained above, usually paragraphs only contain inline-level content.
30/// However, you can integrate any kind of block-level content into a paragraph
31/// by wrapping it in a [`box`].
32///
33/// Conversely, you can separate inline-level content from a paragraph by
34/// wrapping it in a [`block`]. In this case, it will not become part of any
35/// paragraph at all. Read the following section for an explanation of why that
36/// matters and how it differs from just adding paragraph breaks around the
37/// content.
38///
39/// # What becomes a paragraph?
40/// When you add inline-level content to your document, Typst will automatically
41/// wrap it in paragraphs. However, a typical document also contains some text
42/// that is not semantically part of a paragraph, for example in a heading or
43/// caption.
44///
45/// The rules for when Typst wraps inline-level content in a paragraph are as
46/// follows:
47///
48/// - All text at the root of a document is wrapped in paragraphs.
49///
50/// - Text in a container (like a `block`) is only wrapped in a paragraph if the
51///   container holds any block-level content. If all of the contents are
52///   inline-level, no paragraph is created.
53///
54/// In the laid-out document, it's not immediately visible whether text became
55/// part of a paragraph. However, it is still important for various reasons:
56///
57/// - Certain paragraph styling like `first-line-indent` will only apply to
58///   proper paragraphs, not any text. Similarly, `par` show rules of course
59///   only trigger on paragraphs.
60///
61/// - A proper distinction between paragraphs and other text helps people who
62///   rely on assistive technologies (such as screen readers) navigate and
63///   understand the document properly. Currently, this only applies to HTML
64///   export since Typst does not yet output accessible PDFs, but support for
65///   this is planned for the near future.
66///
67/// - HTML export will generate a `<p>` tag only for paragraphs.
68///
69/// When creating custom reusable components, you can and should take charge
70/// over whether Typst creates paragraphs. By wrapping text in a [`block`]
71/// instead of just adding paragraph breaks around it, you can force the absence
72/// of a paragraph. Conversely, by adding a [`parbreak`] after some content in a
73/// container, you can force it to become a paragraph even if it's just one
74/// word. This is, for example, what [non-`tight`]($list.tight) lists do to
75/// force their items to become paragraphs.
76///
77/// # Example
78/// ```example
79/// #set par(
80///   first-line-indent: 1em,
81///   spacing: 0.65em,
82///   justify: true,
83/// )
84///
85/// We proceed by contradiction.
86/// Suppose that there exists a set
87/// of positive integers $a$, $b$, and
88/// $c$ that satisfies the equation
89/// $a^n + b^n = c^n$ for some
90/// integer value of $n > 2$.
91///
92/// Without loss of generality,
93/// let $a$ be the smallest of the
94/// three integers. Then, we ...
95/// ```
96#[elem(scope, title = "Paragraph")]
97pub struct ParElem {
98    /// The spacing between lines.
99    ///
100    /// Leading defines the spacing between the [bottom edge]($text.bottom-edge)
101    /// of one line and the [top edge]($text.top-edge) of the following line. By
102    /// default, these two properties are up to the font, but they can also be
103    /// configured manually with a text set rule.
104    ///
105    /// By setting top edge, bottom edge, and leading, you can also configure a
106    /// consistent baseline-to-baseline distance. You could, for instance, set
107    /// the leading to `{1em}`, the top-edge to `{0.8em}`, and the bottom-edge
108    /// to `{-0.2em}` to get a baseline gap of exactly `{2em}`. The exact
109    /// distribution of the top- and bottom-edge values affects the bounds of
110    /// the first and last line.
111    #[resolve]
112    #[default(Em::new(0.65).into())]
113    pub leading: Length,
114
115    /// The spacing between paragraphs.
116    ///
117    /// Just like leading, this defines the spacing between the bottom edge of a
118    /// paragraph's last line and the top edge of the next paragraph's first
119    /// line.
120    ///
121    /// When a paragraph is adjacent to a [`block`] that is not a paragraph,
122    /// that block's [`above`]($block.above) or [`below`]($block.below) property
123    /// takes precedence over the paragraph spacing. Headings, for instance,
124    /// reduce the spacing below them by default for a better look.
125    #[resolve]
126    #[default(Em::new(1.2).into())]
127    pub spacing: Length,
128
129    /// Whether to justify text in its line.
130    ///
131    /// Hyphenation will be enabled for justified paragraphs if the
132    /// [text function's `hyphenate` property]($text.hyphenate) is set to
133    /// `{auto}` and the current language is known.
134    ///
135    /// Note that the current [alignment]($align.alignment) still has an effect
136    /// on the placement of the last line except if it ends with a
137    /// [justified line break]($linebreak.justify).
138    #[default(false)]
139    pub justify: bool,
140
141    /// How to determine line breaks.
142    ///
143    /// When this property is set to `{auto}`, its default value, optimized line
144    /// breaks will be used for justified paragraphs. Enabling optimized line
145    /// breaks for ragged paragraphs may also be worthwhile to improve the
146    /// appearance of the text.
147    ///
148    /// ```example
149    /// #set page(width: 207pt)
150    /// #set par(linebreaks: "simple")
151    /// Some texts feature many longer
152    /// words. Those are often exceedingly
153    /// challenging to break in a visually
154    /// pleasing way.
155    ///
156    /// #set par(linebreaks: "optimized")
157    /// Some texts feature many longer
158    /// words. Those are often exceedingly
159    /// challenging to break in a visually
160    /// pleasing way.
161    /// ```
162    pub linebreaks: Smart<Linebreaks>,
163
164    /// The indent the first line of a paragraph should have.
165    ///
166    /// By default, only the first line of a consecutive paragraph will be
167    /// indented (not the first one in the document or container, and not
168    /// paragraphs immediately following other block-level elements).
169    ///
170    /// If you want to indent all paragraphs instead, you can pass a dictionary
171    /// containing the `amount` of indent as a length and the pair
172    /// `{all: true}`. When `all` is omitted from the dictionary, it defaults to
173    /// `{false}`.
174    ///
175    /// By typographic convention, paragraph breaks are indicated either by some
176    /// space between paragraphs or by indented first lines. Consider
177    /// - reducing the [paragraph `spacing`]($par.spacing) to the
178    ///   [`leading`]($par.leading) using `{set par(spacing: 0.65em)}`
179    /// - increasing the [block `spacing`]($block.spacing) (which inherits the
180    ///   paragraph spacing by default) to the original paragraph spacing using
181    ///   `{set block(spacing: 1.2em)}`
182    ///
183    /// ```example
184    /// #set block(spacing: 1.2em)
185    /// #set par(
186    ///   first-line-indent: 1.5em,
187    ///   spacing: 0.65em,
188    /// )
189    ///
190    /// The first paragraph is not affected
191    /// by the indent.
192    ///
193    /// But the second paragraph is.
194    ///
195    /// #line(length: 100%)
196    ///
197    /// #set par(first-line-indent: (
198    ///   amount: 1.5em,
199    ///   all: true,
200    /// ))
201    ///
202    /// Now all paragraphs are affected
203    /// by the first line indent.
204    ///
205    /// Even the first one.
206    /// ```
207    pub first_line_indent: FirstLineIndent,
208
209    /// The indent that all but the first line of a paragraph should have.
210    ///
211    /// ```example
212    /// #set par(hanging-indent: 1em)
213    ///
214    /// #lorem(15)
215    /// ```
216    #[resolve]
217    pub hanging_indent: Length,
218
219    /// The contents of the paragraph.
220    #[required]
221    pub body: Content,
222}
223
224#[scope]
225impl ParElem {
226    #[elem]
227    type ParLine;
228}
229
230/// How to determine line breaks in a paragraph.
231#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
232pub enum Linebreaks {
233    /// Determine the line breaks in a simple first-fit style.
234    Simple,
235    /// Optimize the line breaks for the whole paragraph.
236    ///
237    /// Typst will try to produce more evenly filled lines of text by
238    /// considering the whole paragraph when calculating line breaks.
239    Optimized,
240}
241
242/// Configuration for first line indent.
243#[derive(Debug, Default, Copy, Clone, PartialEq, Hash)]
244pub struct FirstLineIndent {
245    /// The amount of indent.
246    pub amount: Length,
247    /// Whether to indent all paragraphs, not just consecutive ones.
248    pub all: bool,
249}
250
251cast! {
252    FirstLineIndent,
253    self => Value::Dict(self.into()),
254    amount: Length => Self { amount, all: false },
255    mut dict: Dict => {
256        let amount = dict.take("amount")?.cast()?;
257        let all = dict.take("all").ok().map(|v| v.cast()).transpose()?.unwrap_or(false);
258        dict.finish(&["amount", "all"])?;
259        Self { amount, all }
260    },
261}
262
263impl From<FirstLineIndent> for Dict {
264    fn from(indent: FirstLineIndent) -> Self {
265        dict! {
266            "amount" => indent.amount,
267            "all" => indent.all,
268        }
269    }
270}
271
272/// A paragraph break.
273///
274/// This starts a new paragraph. Especially useful when used within code like
275/// [for loops]($scripting/#loops). Multiple consecutive
276/// paragraph breaks collapse into a single one.
277///
278/// # Example
279/// ```example
280/// #for i in range(3) {
281///   [Blind text #i: ]
282///   lorem(5)
283///   parbreak()
284/// }
285/// ```
286///
287/// # Syntax
288/// Instead of calling this function, you can insert a blank line into your
289/// markup to create a paragraph break.
290#[elem(title = "Paragraph Break", Unlabellable)]
291pub struct ParbreakElem {}
292
293impl ParbreakElem {
294    /// Get the globally shared paragraph element.
295    pub fn shared() -> &'static Content {
296        singleton!(Content, ParbreakElem::new().pack())
297    }
298}
299
300impl Unlabellable for Packed<ParbreakElem> {}
301
302/// A paragraph line.
303///
304/// This element is exclusively used for line number configuration through set
305/// rules and cannot be placed.
306///
307/// The [`numbering`]($par.line.numbering) option is used to enable line
308/// numbers by specifying a numbering format.
309///
310/// ```example
311/// >>> #set page(margin: (left: 3em))
312/// #set par.line(numbering: "1")
313///
314/// Roses are red. \
315/// Violets are blue. \
316/// Typst is there for you.
317/// ```
318///
319/// The `numbering` option takes either a predefined
320/// [numbering pattern]($numbering) or a function returning styled content. You
321/// can disable line numbers for text inside certain elements by setting the
322/// numbering to `{none}` using show-set rules.
323///
324/// ```example
325/// >>> #set page(margin: (left: 3em))
326/// // Styled red line numbers.
327/// #set par.line(
328///   numbering: n => text(red)[#n]
329/// )
330///
331/// // Disable numbers inside figures.
332/// #show figure: set par.line(
333///   numbering: none
334/// )
335///
336/// Roses are red. \
337/// Violets are blue.
338///
339/// #figure(
340///   caption: [Without line numbers.]
341/// )[
342///   Lorem ipsum \
343///   dolor sit amet
344/// ]
345///
346/// The text above is a sample \
347/// originating from distant times.
348/// ```
349///
350/// This element exposes further options which may be used to control other
351/// aspects of line numbering, such as its [alignment]($par.line.number-align)
352/// or [margin]($par.line.number-margin). In addition, you can control whether
353/// the numbering is reset on each page through the
354/// [`numbering-scope`]($par.line.numbering-scope) option.
355#[elem(name = "line", title = "Paragraph Line", keywords = ["line numbering"], Construct, Locatable)]
356pub struct ParLine {
357    /// How to number each line. Accepts a
358    /// [numbering pattern or function]($numbering).
359    ///
360    /// ```example
361    /// >>> #set page(margin: (left: 3em))
362    /// #set par.line(numbering: "I")
363    ///
364    /// Roses are red. \
365    /// Violets are blue. \
366    /// Typst is there for you.
367    /// ```
368    #[ghost]
369    pub numbering: Option<Numbering>,
370
371    /// The alignment of line numbers associated with each line.
372    ///
373    /// The default of `{auto}` indicates a smart default where numbers grow
374    /// horizontally away from the text, considering the margin they're in and
375    /// the current text direction.
376    ///
377    /// ```example
378    /// >>> #set page(margin: (left: 3em))
379    /// #set par.line(
380    ///   numbering: "I",
381    ///   number-align: left,
382    /// )
383    ///
384    /// Hello world! \
385    /// Today is a beautiful day \
386    /// For exploring the world.
387    /// ```
388    #[ghost]
389    pub number_align: Smart<HAlignment>,
390
391    /// The margin at which line numbers appear.
392    ///
393    /// _Note:_ In a multi-column document, the line numbers for paragraphs
394    /// inside the last column will always appear on the `{end}` margin (right
395    /// margin for left-to-right text and left margin for right-to-left),
396    /// regardless of this configuration. That behavior cannot be changed at
397    /// this moment.
398    ///
399    /// ```example
400    /// >>> #set page(margin: (right: 3em))
401    /// #set par.line(
402    ///   numbering: "1",
403    ///   number-margin: right,
404    /// )
405    ///
406    /// = Report
407    /// - Brightness: Dark, yet darker
408    /// - Readings: Negative
409    /// ```
410    #[ghost]
411    #[default(OuterHAlignment::Start)]
412    pub number_margin: OuterHAlignment,
413
414    /// The distance between line numbers and text.
415    ///
416    /// The default value of `{auto}` results in a clearance that is adaptive to
417    /// the page width and yields reasonable results in most cases.
418    ///
419    /// ```example
420    /// >>> #set page(margin: (left: 3em))
421    /// #set par.line(
422    ///   numbering: "1",
423    ///   number-clearance: 4pt,
424    /// )
425    ///
426    /// Typesetting \
427    /// Styling \
428    /// Layout
429    /// ```
430    #[ghost]
431    #[default]
432    pub number_clearance: Smart<Length>,
433
434    /// Controls when to reset line numbering.
435    ///
436    /// _Note:_ The line numbering scope must be uniform across each page run (a
437    /// page run is a sequence of pages without an explicit pagebreak in
438    /// between). For this reason, set rules for it should be defined before any
439    /// page content, typically at the very start of the document.
440    ///
441    /// ```example
442    /// >>> #set page(margin: (left: 3em))
443    /// #set par.line(
444    ///   numbering: "1",
445    ///   numbering-scope: "page",
446    /// )
447    ///
448    /// First line \
449    /// Second line
450    /// #pagebreak()
451    /// First line again \
452    /// Second line again
453    /// ```
454    #[ghost]
455    #[default(LineNumberingScope::Document)]
456    pub numbering_scope: LineNumberingScope,
457}
458
459impl Construct for ParLine {
460    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
461        bail!(args.span, "cannot be constructed manually");
462    }
463}
464
465/// Possible line numbering scope options, indicating how often the line number
466/// counter should be reset.
467///
468/// Note that, currently, manually resetting the line number counter is not
469/// supported.
470#[derive(Debug, Cast, Clone, Copy, PartialEq, Eq, Hash)]
471pub enum LineNumberingScope {
472    /// Indicates that the line number counter spans the whole document, i.e.,
473    /// it's never automatically reset.
474    Document,
475    /// Indicates that the line number counter should be reset at the start of
476    /// every new page.
477    Page,
478}
479
480/// A marker used to indicate the presence of a line.
481///
482/// This element is added to each line in a paragraph and later searched to
483/// find out where to add line numbers.
484#[elem(Construct, Locatable, Count)]
485pub struct ParLineMarker {
486    #[internal]
487    #[required]
488    pub numbering: Numbering,
489
490    #[internal]
491    #[required]
492    pub number_align: Smart<HAlignment>,
493
494    #[internal]
495    #[required]
496    pub number_margin: OuterHAlignment,
497
498    #[internal]
499    #[required]
500    pub number_clearance: Smart<Length>,
501}
502
503impl Construct for ParLineMarker {
504    fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
505        bail!(args.span, "cannot be constructed manually");
506    }
507}
508
509impl Count for Packed<ParLineMarker> {
510    fn update(&self) -> Option<CounterUpdate> {
511        // The line counter must be updated manually by the root flow.
512        None
513    }
514}