diff options
Diffstat (limited to 'crates/typst-library/src/model/par.rs')
| -rw-r--r-- | crates/typst-library/src/model/par.rs | 431 |
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 + } +} |
