summaryrefslogtreecommitdiff
path: root/crates/typst-library/src/model/par.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library/src/model/par.rs')
-rw-r--r--crates/typst-library/src/model/par.rs431
1 files changed, 431 insertions, 0 deletions
diff --git a/crates/typst-library/src/model/par.rs b/crates/typst-library/src/model/par.rs
new file mode 100644
index 00000000..a43499d8
--- /dev/null
+++ b/crates/typst-library/src/model/par.rs
@@ -0,0 +1,431 @@
+use std::fmt::{self, Debug, Formatter};
+
+use typst_utils::singleton;
+
+use crate::diag::{bail, SourceResult};
+use crate::engine::Engine;
+use crate::foundations::{
+ elem, scope, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart,
+ StyleVec, Unlabellable,
+};
+use crate::introspection::{Count, CounterUpdate, Locatable};
+use crate::layout::{Em, HAlignment, Length, OuterHAlignment};
+use crate::model::Numbering;
+
+/// Arranges text, spacing and inline-level elements into a paragraph.
+///
+/// Although this function is primarily used in set rules to affect paragraph
+/// properties, it can also be used to explicitly render its argument onto a
+/// paragraph of its own.
+///
+/// # Example
+/// ```example
+/// #set par(
+/// first-line-indent: 1em,
+/// spacing: 0.65em,
+/// justify: true,
+/// )
+///
+/// We proceed by contradiction.
+/// Suppose that there exists a set
+/// of positive integers $a$, $b$, and
+/// $c$ that satisfies the equation
+/// $a^n + b^n = c^n$ for some
+/// integer value of $n > 2$.
+///
+/// Without loss of generality,
+/// let $a$ be the smallest of the
+/// three integers. Then, we ...
+/// ```
+#[elem(scope, title = "Paragraph", Debug, Construct)]
+pub struct ParElem {
+ /// The spacing between lines.
+ ///
+ /// Leading defines the spacing between the [bottom edge]($text.bottom-edge)
+ /// of one line and the [top edge]($text.top-edge) of the following line. By
+ /// default, these two properties are up to the font, but they can also be
+ /// configured manually with a text set rule.
+ ///
+ /// By setting top edge, bottom edge, and leading, you can also configure a
+ /// consistent baseline-to-baseline distance. You could, for instance, set
+ /// the leading to `{1em}`, the top-edge to `{0.8em}`, and the bottom-edge
+ /// to `{-0.2em}` to get a baseline gap of exactly `{2em}`. The exact
+ /// distribution of the top- and bottom-edge values affects the bounds of
+ /// the first and last line.
+ #[resolve]
+ #[ghost]
+ #[default(Em::new(0.65).into())]
+ pub leading: Length,
+
+ /// The spacing between paragraphs.
+ ///
+ /// Just like leading, this defines the spacing between the bottom edge of a
+ /// paragraph's last line and the top edge of the next paragraph's first
+ /// line.
+ ///
+ /// When a paragraph is adjacent to a [`block`] that is not a paragraph,
+ /// that block's [`above`]($block.above) or [`below`]($block.below) property
+ /// takes precedence over the paragraph spacing. Headings, for instance,
+ /// reduce the spacing below them by default for a better look.
+ #[resolve]
+ #[ghost]
+ #[default(Em::new(1.2).into())]
+ pub spacing: Length,
+
+ /// Whether to justify text in its line.
+ ///
+ /// Hyphenation will be enabled for justified paragraphs if the
+ /// [text function's `hyphenate` property]($text.hyphenate) is set to
+ /// `{auto}` and the current language is known.
+ ///
+ /// Note that the current [alignment]($align.alignment) still has an effect
+ /// on the placement of the last line except if it ends with a
+ /// [justified line break]($linebreak.justify).
+ #[ghost]
+ #[default(false)]
+ pub justify: bool,
+
+ /// How to determine line breaks.
+ ///
+ /// When this property is set to `{auto}`, its default value, optimized line
+ /// breaks will be used for justified paragraphs. Enabling optimized line
+ /// breaks for ragged paragraphs may also be worthwhile to improve the
+ /// appearance of the text.
+ ///
+ /// ```example
+ /// #set page(width: 207pt)
+ /// #set par(linebreaks: "simple")
+ /// Some texts feature many longer
+ /// words. Those are often exceedingly
+ /// challenging to break in a visually
+ /// pleasing way.
+ ///
+ /// #set par(linebreaks: "optimized")
+ /// Some texts feature many longer
+ /// words. Those are often exceedingly
+ /// challenging to break in a visually
+ /// pleasing way.
+ /// ```
+ #[ghost]
+ pub linebreaks: Smart<Linebreaks>,
+
+ /// The indent the first line of a paragraph should have.
+ ///
+ /// Only the first line of a consecutive paragraph will be indented (not
+ /// the first one in a block or on the page).
+ ///
+ /// By typographic convention, paragraph breaks are indicated either by some
+ /// space between paragraphs or by indented first lines. Consider reducing
+ /// the [paragraph spacing]($block.spacing) to the [`leading`]($par.leading)
+ /// when using this property (e.g. using `[#set par(spacing: 0.65em)]`).
+ #[ghost]
+ pub first_line_indent: Length,
+
+ /// The indent all but the first line of a paragraph should have.
+ #[ghost]
+ #[resolve]
+ pub hanging_indent: Length,
+
+ /// Indicates whether an overflowing line should be shrunk.
+ ///
+ /// This property is set to `false` on raw blocks, because shrinking a line
+ /// could visually break the indentation.
+ #[ghost]
+ #[internal]
+ #[default(true)]
+ pub shrink: bool,
+
+ /// The contents of the paragraph.
+ #[external]
+ #[required]
+ pub body: Content,
+
+ /// The paragraph's children.
+ #[internal]
+ #[variadic]
+ pub children: StyleVec,
+}
+
+#[scope]
+impl ParElem {
+ #[elem]
+ type ParLine;
+}
+
+impl Construct for ParElem {
+ fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content> {
+ // The paragraph constructor is special: It doesn't create a paragraph
+ // element. Instead, it just ensures that the passed content lives in a
+ // separate paragraph and styles it.
+ let styles = Self::set(engine, args)?;
+ let body = args.expect::<Content>("body")?;
+ Ok(Content::sequence([
+ ParbreakElem::shared().clone(),
+ body.styled_with_map(styles),
+ ParbreakElem::shared().clone(),
+ ]))
+ }
+}
+
+impl Debug for ParElem {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Par ")?;
+ self.children.fmt(f)
+ }
+}
+
+/// How to determine line breaks in a paragraph.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
+pub enum Linebreaks {
+ /// Determine the line breaks in a simple first-fit style.
+ Simple,
+ /// Optimize the line breaks for the whole paragraph.
+ ///
+ /// Typst will try to produce more evenly filled lines of text by
+ /// considering the whole paragraph when calculating line breaks.
+ Optimized,
+}
+
+/// A paragraph break.
+///
+/// This starts a new paragraph. Especially useful when used within code like
+/// [for loops]($scripting/#loops). Multiple consecutive
+/// paragraph breaks collapse into a single one.
+///
+/// # Example
+/// ```example
+/// #for i in range(3) {
+/// [Blind text #i: ]
+/// lorem(5)
+/// parbreak()
+/// }
+/// ```
+///
+/// # Syntax
+/// Instead of calling this function, you can insert a blank line into your
+/// markup to create a paragraph break.
+#[elem(title = "Paragraph Break", Unlabellable)]
+pub struct ParbreakElem {}
+
+impl ParbreakElem {
+ /// Get the globally shared paragraph element.
+ pub fn shared() -> &'static Content {
+ singleton!(Content, ParbreakElem::new().pack())
+ }
+}
+
+impl Unlabellable for Packed<ParbreakElem> {}
+
+/// A paragraph line.
+///
+/// This element is exclusively used for line number configuration through set
+/// rules and cannot be placed.
+///
+/// The [`numbering`]($par.line.numbering) option is used to enable line
+/// numbers by specifying a numbering format.
+///
+/// ```example
+/// >>> #set page(margin: (left: 3em))
+/// #set par.line(numbering: "1")
+///
+/// Roses are red. \
+/// Violets are blue. \
+/// Typst is there for you.
+/// ```
+///
+/// The `numbering` option takes either a predefined
+/// [numbering pattern]($numbering) or a function returning styled content. You
+/// can disable line numbers for text inside certain elements by setting the
+/// numbering to `{none}` using show-set rules.
+///
+/// ```example
+/// >>> #set page(margin: (left: 3em))
+/// // Styled red line numbers.
+/// #set par.line(
+/// numbering: n => text(red)[#n]
+/// )
+///
+/// // Disable numbers inside figures.
+/// #show figure: set par.line(
+/// numbering: none
+/// )
+///
+/// Roses are red. \
+/// Violets are blue.
+///
+/// #figure(
+/// caption: [Without line numbers.]
+/// )[
+/// Lorem ipsum \
+/// dolor sit amet
+/// ]
+///
+/// The text above is a sample \
+/// originating from distant times.
+/// ```
+///
+/// This element exposes further options which may be used to control other
+/// aspects of line numbering, such as its [alignment]($par.line.number-align)
+/// or [margin]($par.line.number-margin). In addition, you can control whether
+/// the numbering is reset on each page through the
+/// [`numbering-scope`]($par.line.numbering-scope) option.
+#[elem(name = "line", title = "Paragraph Line", keywords = ["line numbering"], Construct, Locatable)]
+pub struct ParLine {
+ /// How to number each line. Accepts a
+ /// [numbering pattern or function]($numbering).
+ ///
+ /// ```example
+ /// >>> #set page(margin: (left: 3em))
+ /// #set par.line(numbering: "I")
+ ///
+ /// Roses are red. \
+ /// Violets are blue. \
+ /// Typst is there for you.
+ /// ```
+ #[ghost]
+ pub numbering: Option<Numbering>,
+
+ /// The alignment of line numbers associated with each line.
+ ///
+ /// The default of `{auto}` indicates a smart default where numbers grow
+ /// horizontally away from the text, considering the margin they're in and
+ /// the current text direction.
+ ///
+ /// ```example
+ /// >>> #set page(margin: (left: 3em))
+ /// #set par.line(
+ /// numbering: "I",
+ /// number-align: left,
+ /// )
+ ///
+ /// Hello world! \
+ /// Today is a beautiful day \
+ /// For exploring the world.
+ /// ```
+ #[ghost]
+ pub number_align: Smart<HAlignment>,
+
+ /// The margin at which line numbers appear.
+ ///
+ /// _Note:_ In a multi-column document, the line numbers for paragraphs
+ /// inside the last column will always appear on the `{end}` margin (right
+ /// margin for left-to-right text and left margin for right-to-left),
+ /// regardless of this configuration. That behavior cannot be changed at
+ /// this moment.
+ ///
+ /// ```example
+ /// >>> #set page(margin: (right: 3em))
+ /// #set par.line(
+ /// numbering: "1",
+ /// number-margin: right,
+ /// )
+ ///
+ /// = Report
+ /// - Brightness: Dark, yet darker
+ /// - Readings: Negative
+ /// ```
+ #[ghost]
+ #[default(OuterHAlignment::Start)]
+ pub number_margin: OuterHAlignment,
+
+ /// The distance between line numbers and text.
+ ///
+ /// The default value of `{auto}` results in a clearance that is adaptive to
+ /// the page width and yields reasonable results in most cases.
+ ///
+ /// ```example
+ /// >>> #set page(margin: (left: 3em))
+ /// #set par.line(
+ /// numbering: "1",
+ /// number-clearance: 4pt,
+ /// )
+ ///
+ /// Typesetting \
+ /// Styling \
+ /// Layout
+ /// ```
+ #[ghost]
+ #[default]
+ pub number_clearance: Smart<Length>,
+
+ /// Controls when to reset line numbering.
+ ///
+ /// _Note:_ The line numbering scope must be uniform across each page run (a
+ /// page run is a sequence of pages without an explicit pagebreak in
+ /// between). For this reason, set rules for it should be defined before any
+ /// page content, typically at the very start of the document.
+ ///
+ /// ```example
+ /// >>> #set page(margin: (left: 3em))
+ /// #set par.line(
+ /// numbering: "1",
+ /// numbering-scope: "page",
+ /// )
+ ///
+ /// First line \
+ /// Second line
+ /// #pagebreak()
+ /// First line again \
+ /// Second line again
+ /// ```
+ #[ghost]
+ #[default(LineNumberingScope::Document)]
+ pub numbering_scope: LineNumberingScope,
+}
+
+impl Construct for ParLine {
+ fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
+ bail!(args.span, "cannot be constructed manually");
+ }
+}
+
+/// Possible line numbering scope options, indicating how often the line number
+/// counter should be reset.
+///
+/// Note that, currently, manually resetting the line number counter is not
+/// supported.
+#[derive(Debug, Cast, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum LineNumberingScope {
+ /// Indicates that the line number counter spans the whole document, i.e.,
+ /// it's never automatically reset.
+ Document,
+ /// Indicates that the line number counter should be reset at the start of
+ /// every new page.
+ Page,
+}
+
+/// A marker used to indicate the presence of a line.
+///
+/// This element is added to each line in a paragraph and later searched to
+/// find out where to add line numbers.
+#[elem(Construct, Locatable, Count)]
+pub struct ParLineMarker {
+ #[internal]
+ #[required]
+ pub numbering: Numbering,
+
+ #[internal]
+ #[required]
+ pub number_align: Smart<HAlignment>,
+
+ #[internal]
+ #[required]
+ pub number_margin: OuterHAlignment,
+
+ #[internal]
+ #[required]
+ pub number_clearance: Smart<Length>,
+}
+
+impl Construct for ParLineMarker {
+ fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
+ bail!(args.span, "cannot be constructed manually");
+ }
+}
+
+impl Count for Packed<ParLineMarker> {
+ fn update(&self) -> Option<CounterUpdate> {
+ // The line counter must be updated manually by the root flow.
+ None
+ }
+}