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}