diff --git a/crates/typst/src/layout/container.rs b/crates/typst/src/layout/container.rs index 908a193e..533d6b7e 100644 --- a/crates/typst/src/layout/container.rs +++ b/crates/typst/src/layout/container.rs @@ -10,7 +10,7 @@ use crate::foundations::{ use crate::introspection::Locator; use crate::layout::{ Abs, Axes, Corners, Em, Fr, Fragment, Frame, FrameKind, Length, Region, Regions, Rel, - Sides, Size, Spacing, VElem, + Sides, Size, Spacing, }; use crate::utils::Numeric; use crate::visualize::{clip_rect, Paint, Stroke}; @@ -385,8 +385,20 @@ pub struct BlockElem { #[fold] pub outset: Sides>>, - /// The spacing around this block. This is shorthand to set `above` and - /// `below` to the same value. + /// The spacing around the block. When `{auto}`, inherits the paragraph + /// [`spacing`]($par.spacing). + /// + /// For two adjacent blocks, the larger of the first block's `above` and the + /// second block's `below` spacing wins. Moreover, block spacing takes + /// precedence over paragraph [`spacing`]($par.spacing). + /// + /// Note that this is only a shorthand to set `above` and `below` to the + /// same value. Since the values for `above` and `below` might differ, a + /// [context] block only provides access to `{block.above}` and + /// `{block.below}`, not to `{block.spacing}` directly. + /// + /// This property can be used in combination with a show rule to adjust the + /// spacing around arbitrary block-level elements. /// /// ```example /// #set align(center) @@ -400,35 +412,16 @@ pub struct BlockElem { #[default(Em::new(1.2).into())] pub spacing: Spacing, - /// The spacing between this block and its predecessor. Takes precedence - /// over `spacing`. Can be used in combination with a show rule to adjust - /// the spacing around arbitrary block-level elements. - #[external] - #[default(Em::new(1.2).into())] - pub above: Spacing, - #[internal] + /// The spacing between this block and its predecessor. #[parse( let spacing = args.named("spacing")?; - args.named("above")? - .map(VElem::block_around) - .or_else(|| spacing.map(VElem::block_spacing)) + args.named("above")?.or(spacing) )] - #[default(VElem::block_spacing(Em::new(1.2).into()))] - pub above: VElem, + pub above: Smart, - /// The spacing between this block and its successor. Takes precedence - /// over `spacing`. - #[external] - #[default(Em::new(1.2).into())] - pub below: Spacing, - #[internal] - #[parse( - args.named("below")? - .map(VElem::block_around) - .or_else(|| spacing.map(VElem::block_spacing)) - )] - #[default(VElem::block_spacing(Em::new(1.2).into()))] - pub below: VElem, + /// The spacing between this block and its successor. + #[parse(args.named("below")?.or(spacing))] + pub below: Smart, /// Whether to clip the content inside the block. #[default(false)] diff --git a/crates/typst/src/layout/spacing.rs b/crates/typst/src/layout/spacing.rs index ddf17e5d..5c02a0cf 100644 --- a/crates/typst/src/layout/spacing.rs +++ b/crates/typst/src/layout/spacing.rs @@ -153,13 +153,13 @@ impl VElem { Self::new(amount).with_weakness(2).with_attach(true) } - /// Weak spacing with BlockElem::ABOVE/BELOW weakness. - pub fn block_around(amount: Spacing) -> Self { + /// Weak spacing with `BlockElem::spacing` weakness. + pub fn block_spacing(amount: Spacing) -> Self { Self::new(amount).with_weakness(3) } - /// Weak spacing with BlockElem::SPACING weakness. - pub fn block_spacing(amount: Spacing) -> Self { + /// Weak spacing with `ParElem::spacing` weakness. + pub fn par_spacing(amount: Spacing) -> Self { Self::new(amount).with_weakness(4) } } diff --git a/crates/typst/src/model/bibliography.rs b/crates/typst/src/model/bibliography.rs index 57a33fc4..9070442e 100644 --- a/crates/typst/src/model/bibliography.rs +++ b/crates/typst/src/model/bibliography.rs @@ -231,7 +231,7 @@ impl Show for Packed { .ok_or("CSL style is not suitable for bibliographies") .at(span)?; - let row_gutter = *BlockElem::below_in(styles).amount(); + let row_gutter = ParElem::spacing_in(styles).into(); if references.iter().any(|(prefix, _)| prefix.is_some()) { let mut cells = vec![]; for (prefix, reference) in references { diff --git a/crates/typst/src/model/enum.rs b/crates/typst/src/model/enum.rs index 9d4cdf42..336a8fc9 100644 --- a/crates/typst/src/model/enum.rs +++ b/crates/typst/src/model/enum.rs @@ -12,7 +12,7 @@ use crate::foundations::{ use crate::introspection::Locator; use crate::layout::{ Alignment, Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, - Length, Regions, Sizing, Spacing, VAlignment, VElem, + Length, Regions, Sizing, VAlignment, VElem, }; use crate::model::{Numbering, NumberingPattern, ParElem}; use crate::text::TextElem; @@ -155,10 +155,12 @@ pub struct EnumElem { #[default(Em::new(0.5).into())] pub body_indent: Length, - /// The spacing between the items of a wide (non-tight) enumeration. + /// The spacing between the items of the enumeration. /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, + /// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight + /// enumerations and paragraph [`spacing`]($par.spacing) for wide + /// (non-tight) enumerations. + pub spacing: Smart, /// The alignment that enum numbers should have. /// @@ -242,12 +244,13 @@ fn layout_enum( let numbering = elem.numbering(styles); let indent = elem.indent(styles); let body_indent = elem.body_indent(styles); - let gutter = if elem.tight(styles) { - ParElem::leading_in(styles).into() - } else { - elem.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; + let gutter = elem.spacing(styles).unwrap_or_else(|| { + if elem.tight(styles) { + ParElem::leading_in(styles).into() + } else { + ParElem::spacing_in(styles).into() + } + }); let mut cells = vec![]; let mut locator = locator.split(); diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs index e160eeea..e065d3ac 100644 --- a/crates/typst/src/model/heading.rs +++ b/crates/typst/src/model/heading.rs @@ -9,9 +9,7 @@ use crate::foundations::{ use crate::introspection::{ Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink, }; -use crate::layout::{ - Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions, VElem, -}; +use crate::layout::{Abs, Axes, BlockChild, BlockElem, Em, HElem, Length, Regions}; use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement}; use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize}; use crate::utils::NonZeroExt; @@ -280,8 +278,8 @@ impl ShowSet for Packed { let mut out = Styles::new(); out.set(TextElem::set_size(TextSize(size.into()))); out.set(TextElem::set_weight(FontWeight::BOLD)); - out.set(BlockElem::set_above(VElem::block_around(above.into()))); - out.set(BlockElem::set_below(VElem::block_around(below.into()))); + out.set(BlockElem::set_above(Smart::Custom(above.into()))); + out.set(BlockElem::set_below(Smart::Custom(below.into()))); out.set(BlockElem::set_sticky(true)); out } diff --git a/crates/typst/src/model/list.rs b/crates/typst/src/model/list.rs index d8e58052..a0d76770 100644 --- a/crates/typst/src/model/list.rs +++ b/crates/typst/src/model/list.rs @@ -9,7 +9,7 @@ use crate::foundations::{ use crate::introspection::Locator; use crate::layout::{ Axes, BlockElem, Cell, CellGrid, Em, Fragment, GridLayouter, HAlignment, Length, - Regions, Sizing, Spacing, VAlignment, VElem, + Regions, Sizing, VAlignment, VElem, }; use crate::model::ParElem; use crate::text::TextElem; @@ -107,10 +107,12 @@ pub struct ListElem { #[default(Em::new(0.5).into())] pub body_indent: Length, - /// The spacing between the items of a wide (non-tight) list. + /// The spacing between the items of the list. /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, + /// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight + /// lists and paragraph [`spacing`]($par.spacing) for wide (non-tight) + /// lists. + pub spacing: Smart, /// The bullet list's children. /// @@ -165,12 +167,13 @@ fn layout_list( ) -> SourceResult { let indent = elem.indent(styles); let body_indent = elem.body_indent(styles); - let gutter = if elem.tight(styles) { - ParElem::leading_in(styles).into() - } else { - elem.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; + let gutter = elem.spacing(styles).unwrap_or_else(|| { + if elem.tight(styles) { + ParElem::leading_in(styles).into() + } else { + ParElem::spacing_in(styles).into() + } + }); let Depth(depth) = ListElem::depth_in(styles); let marker = elem diff --git a/crates/typst/src/model/par.rs b/crates/typst/src/model/par.rs index 8d5178b1..7f65a00f 100644 --- a/crates/typst/src/model/par.rs +++ b/crates/typst/src/model/par.rs @@ -38,11 +38,38 @@ use crate::realize::StyleVec; #[elem(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 diff --git a/crates/typst/src/model/quote.rs b/crates/typst/src/model/quote.rs index 6f7f0718..c35d895f 100644 --- a/crates/typst/src/model/quote.rs +++ b/crates/typst/src/model/quote.rs @@ -222,15 +222,14 @@ impl Show for Packed { } impl ShowSet for Packed { - fn show_set(&self, _: StyleChain) -> Styles { - let x = Em::new(1.0).into(); - let above = Em::new(2.4).into(); - let below = Em::new(1.8).into(); + fn show_set(&self, styles: StyleChain) -> Styles { let mut out = Styles::new(); - out.set(PadElem::set_left(x)); - out.set(PadElem::set_right(x)); - out.set(BlockElem::set_above(VElem::block_around(above))); - out.set(BlockElem::set_below(VElem::block_around(below))); + if self.block(styles) { + out.set(PadElem::set_left(Em::new(1.0).into())); + out.set(PadElem::set_right(Em::new(1.0).into())); + out.set(BlockElem::set_above(Smart::Custom(Em::new(2.4).into()))); + out.set(BlockElem::set_below(Smart::Custom(Em::new(1.8).into()))); + } out } } diff --git a/crates/typst/src/model/terms.rs b/crates/typst/src/model/terms.rs index cb88b02d..98fe3ff1 100644 --- a/crates/typst/src/model/terms.rs +++ b/crates/typst/src/model/terms.rs @@ -4,9 +4,7 @@ use crate::foundations::{ cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain, Styles, }; -use crate::layout::{ - BlockElem, Dir, Em, HElem, Length, Sides, Spacing, StackChild, StackElem, VElem, -}; +use crate::layout::{Dir, Em, HElem, Length, Sides, StackChild, StackElem, VElem}; use crate::model::ParElem; use crate::text::TextElem; use crate::utils::Numeric; @@ -82,10 +80,12 @@ pub struct TermsElem { #[default(Em::new(2.0).into())] pub hanging_indent: Length, - /// The spacing between the items of a wide (non-tight) term list. + /// The spacing between the items of the term list. /// - /// If set to `{auto}`, uses the spacing [below blocks]($block.below). - pub spacing: Smart, + /// If set to `{auto}`, uses paragraph [`leading`]($par.leading) for tight + /// term lists and paragraph [`spacing`]($par.spacing) for wide + /// (non-tight) term lists. + pub spacing: Smart, /// The term list's children. /// @@ -114,12 +114,13 @@ impl Show for Packed { let separator = self.separator(styles); let indent = self.indent(styles); let hanging_indent = self.hanging_indent(styles); - let gutter = if self.tight(styles) { - ParElem::leading_in(styles).into() - } else { - self.spacing(styles) - .unwrap_or_else(|| *BlockElem::below_in(styles).amount()) - }; + let gutter = self.spacing(styles).unwrap_or_else(|| { + if self.tight(styles) { + ParElem::leading_in(styles).into() + } else { + ParElem::spacing_in(styles).into() + } + }); let pad = hanging_indent + indent; let unpad = (!hanging_indent.is_zero()) @@ -143,7 +144,7 @@ impl Show for Packed { } let mut realized = StackElem::new(children) - .with_spacing(Some(gutter)) + .with_spacing(Some(gutter.into())) .pack() .padded(padding); diff --git a/crates/typst/src/realize/mod.rs b/crates/typst/src/realize/mod.rs index caad222a..005c399b 100644 --- a/crates/typst/src/realize/mod.rs +++ b/crates/typst/src/realize/mod.rs @@ -10,6 +10,8 @@ mod arenas; mod behaviour; mod process; +use once_cell::unsync::Lazy; + pub use self::arenas::Arenas; pub use self::behaviour::{Behave, BehavedBuilder, Behaviour, StyleVec}; pub use self::process::process; @@ -19,7 +21,7 @@ use std::mem; use crate::diag::{bail, SourceResult}; use crate::engine::{Engine, Route}; use crate::foundations::{ - Content, NativeElement, Packed, SequenceElem, StyleChain, StyledElem, Styles, + Content, NativeElement, Packed, SequenceElem, Smart, StyleChain, StyledElem, Styles, }; use crate::introspection::{Locator, SplitLocator, TagElem}; use crate::layout::{ @@ -407,17 +409,31 @@ impl<'a> FlowBuilder<'a> { return true; } + let par_spacing = Lazy::new(|| { + arenas.store(VElem::par_spacing(ParElem::spacing_in(styles).into()).pack()) + }); + if let Some(elem) = content.to_packed::() { - self.0.push(arenas.store(elem.above(styles).pack()), styles); + let above = match elem.above(styles) { + Smart::Auto => *par_spacing, + Smart::Custom(above) => arenas.store(VElem::block_spacing(above).pack()), + }; + + let below = match elem.below(styles) { + Smart::Auto => *par_spacing, + Smart::Custom(below) => arenas.store(VElem::block_spacing(below).pack()), + }; + + self.0.push(above, styles); self.0.push(content, styles); - self.0.push(arenas.store(elem.below(styles).pack()), styles); + self.0.push(below, styles); return true; } if content.is::() { - self.0.push(arenas.store(BlockElem::above_in(styles).pack()), styles); + self.0.push(*par_spacing, styles); self.0.push(content, styles); - self.0.push(arenas.store(BlockElem::below_in(styles).pack()), styles); + self.0.push(*par_spacing, styles); return true; } diff --git a/tests/ref/par-leading-and-block-spacing.png b/tests/ref/par-leading-and-spacing.png similarity index 100% rename from tests/ref/par-leading-and-block-spacing.png rename to tests/ref/par-leading-and-spacing.png diff --git a/tests/ref/query-before-after.png b/tests/ref/query-before-after.png index 33fde985..32b59573 100644 Binary files a/tests/ref/query-before-after.png and b/tests/ref/query-before-after.png differ diff --git a/tests/ref/quote-cite-format-author-date.png b/tests/ref/quote-cite-format-author-date.png index 43816f8c..dd47223f 100644 Binary files a/tests/ref/quote-cite-format-author-date.png and b/tests/ref/quote-cite-format-author-date.png differ diff --git a/tests/ref/quote-cite-format-label-or-numeric.png b/tests/ref/quote-cite-format-label-or-numeric.png index f0f5f90f..ce698862 100644 Binary files a/tests/ref/quote-cite-format-label-or-numeric.png and b/tests/ref/quote-cite-format-label-or-numeric.png differ diff --git a/tests/ref/quote-cite-format-note.png b/tests/ref/quote-cite-format-note.png index cb2c4c0a..03e0088a 100644 Binary files a/tests/ref/quote-cite-format-note.png and b/tests/ref/quote-cite-format-note.png differ diff --git a/tests/ref/quote-dir-author-pos.png b/tests/ref/quote-dir-author-pos.png index 842796a2..81041f50 100644 Binary files a/tests/ref/quote-dir-author-pos.png and b/tests/ref/quote-dir-author-pos.png differ diff --git a/tests/ref/quote-inline.png b/tests/ref/quote-inline.png index 4dbc9720..6d372283 100644 Binary files a/tests/ref/quote-inline.png and b/tests/ref/quote-inline.png differ diff --git a/tests/suite/introspection/query.typ b/tests/suite/introspection/query.typ index 6cdd4bab..76ecc913 100644 --- a/tests/suite/introspection/query.typ +++ b/tests/suite/introspection/query.typ @@ -85,7 +85,7 @@ #set text(size: 12pt, weight: "regular") #outline( - title: "Chapter outline", + title: none, indent: true, target: heading .where(level: 1) diff --git a/tests/suite/layout/container.typ b/tests/suite/layout/container.typ index b6d30f30..2c68099a 100644 --- a/tests/suite/layout/container.typ +++ b/tests/suite/layout/container.typ @@ -43,13 +43,26 @@ First! ] --- block-spacing-basic --- -#set block(spacing: 10pt) +#set par(spacing: 10pt) Hello There #block(spacing: 20pt)[Further down] +--- block-above-below-context --- +#context test(block.above, auto) +#set block(spacing: 20pt) +#context test(block.above, 20pt) +#context test(block.below, 20pt) + +--- block-spacing-context --- +// The values for `above` and `below` might be different, so we cannot retrieve +// `spacing` directly +// +// Error: 16-23 function `block` does not contain field `spacing` +#context block.spacing + --- block-spacing-table --- // Test that paragraph spacing loses against block spacing. #set block(spacing: 100pt) diff --git a/tests/suite/layout/inline/justify.typ b/tests/suite/layout/inline/justify.typ index e1e15578..b35ff1fd 100644 --- a/tests/suite/layout/inline/justify.typ +++ b/tests/suite/layout/inline/justify.typ @@ -1,7 +1,6 @@ --- justify --- #set page(width: 180pt) -#set block(spacing: 5pt) -#set par(justify: true, first-line-indent: 14pt, leading: 5pt) +#set par(justify: true, first-line-indent: 14pt, spacing: 5pt, leading: 5pt) This text is justified, meaning that spaces are stretched so that the text forms a "block" with flush edges at both sides. diff --git a/tests/suite/model/list.typ b/tests/suite/model/list.typ index e37fa65d..4d3f402d 100644 --- a/tests/suite/model/list.typ +++ b/tests/suite/model/list.typ @@ -129,7 +129,7 @@ More. --- list-wide-cannot-attach --- // Test that wide lists cannot be ... -#set block(spacing: 15pt) +#set par(spacing: 15pt) Hello - A diff --git a/tests/suite/model/outline.typ b/tests/suite/model/outline.typ index d8fc1a43..085e06ed 100644 --- a/tests/suite/model/outline.typ +++ b/tests/suite/model/outline.typ @@ -118,6 +118,7 @@ Ok ... } #outline(indent: auto) +#v(1.2em, weak: true) #set text(8pt) #show heading: set block(spacing: 0.65em) @@ -142,6 +143,7 @@ Ok ... #counter(page).update(3) #outline(indent: auto, fill: repeat[--]) +#v(1.2em, weak: true) #set text(8pt) #show heading: set block(spacing: 0.65em) diff --git a/tests/suite/model/par.typ b/tests/suite/model/par.typ index 65779f6a..f07c4c6c 100644 --- a/tests/suite/model/par.typ +++ b/tests/suite/model/par.typ @@ -19,17 +19,19 @@ heaven Would through the airy region stream so bright That birds would sing and think it were not night. See, how she leans her cheek upon her hand! O, that I were a glove upon that hand, That I might touch that cheek! ---- par-leading-and-block-spacing --- +--- par-leading-and-spacing --- // Test changing leading and spacing. -#set block(spacing: 1em) -#set par(leading: 2pt) +#set par(spacing: 1em, leading: 2pt) But, soft! what light through yonder window breaks? It is the east, and Juliet is the sun. +--- par-spacing-context --- +#set par(spacing: 10pt) +#context test(par.spacing, 10pt) + --- par-first-line-indent --- -#set par(first-line-indent: 12pt, leading: 5pt) -#set block(spacing: 5pt) +#set par(first-line-indent: 12pt, spacing: 5pt, leading: 5pt) #show heading: set text(size: 10pt) The first paragraph has no indent. diff --git a/tests/suite/model/quote.typ b/tests/suite/model/quote.typ index 446784ee..d71eeeab 100644 --- a/tests/suite/model/quote.typ +++ b/tests/suite/model/quote.typ @@ -29,7 +29,7 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum]. #set text(8pt) #quote(attribution: )[In a hole in the ground there lived a hobbit.] -#set text(0pt) +#show bibliography: none #bibliography("/assets/bib/works.bib") --- quote-cite-format-label-or-numeric --- @@ -38,7 +38,7 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum]. #set quote(block: true) #quote(attribution: )[In a hole in the ground there lived a hobbit.] -#set text(0pt) +#show bibliography: none #bibliography("/assets/bib/works.bib", style: "ieee") --- quote-cite-format-note --- @@ -47,7 +47,7 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum]. #set quote(block: true) #quote(attribution: )[In a hole in the ground there lived a hobbit.] -#set text(0pt) +#show bibliography: none #bibliography("/assets/bib/works.bib", style: "chicago-notes") --- quote-cite-format-author-date --- @@ -56,7 +56,7 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum]. #set quote(block: true) #quote(attribution: )[In a hole in the ground there lived a hobbit.] -#set text(0pt) +#show bibliography: none #bibliography("/assets/bib/works.bib", style: "apa") --- quote-nesting --- diff --git a/tests/suite/styling/set.typ b/tests/suite/styling/set.typ index a31cd165..ca080977 100644 --- a/tests/suite/styling/set.typ +++ b/tests/suite/styling/set.typ @@ -21,7 +21,7 @@ Hello *#x* --- set-text-override --- // Test that that block spacing and text style are respected from // the outside, but the more specific fill is respected. -#set block(spacing: 4pt) +#set par(spacing: 4pt) #set text(style: "italic", fill: eastern) #let x = [And the forest #parbreak() lay silent!] #text(fill: forest, x)