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}