Paragraph spacing property (#4390)

This commit is contained in:
Laurenz 2024-06-14 10:49:44 +02:00 committed by GitHub
parent 6f9855a8c3
commit 9a45d948f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 155 additions and 99 deletions

View File

@ -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<Option<Rel<Length>>>,
/// 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<Spacing>,
/// 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<Spacing>,
/// Whether to clip the content inside the block.
#[default(false)]

View File

@ -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)
}
}

View File

@ -231,7 +231,7 @@ impl Show for Packed<BibliographyElem> {
.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 {

View File

@ -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<Spacing>,
/// 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<Length>,
/// 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();

View File

@ -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<HeadingElem> {
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
}

View File

@ -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<Spacing>,
/// 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<Length>,
/// The bullet list's children.
///
@ -165,12 +167,13 @@ fn layout_list(
) -> SourceResult<Fragment> {
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

View File

@ -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

View File

@ -222,15 +222,14 @@ impl Show for Packed<QuoteElem> {
}
impl ShowSet for Packed<QuoteElem> {
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
}
}

View File

@ -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<Spacing>,
/// 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<Length>,
/// The term list's children.
///
@ -114,12 +114,13 @@ impl Show for Packed<TermsElem> {
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<TermsElem> {
}
let mut realized = StackElem::new(children)
.with_spacing(Some(gutter))
.with_spacing(Some(gutter.into()))
.pack()
.padded(padding);

View File

@ -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::<BlockElem>() {
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::<ParElem>() {
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;
}

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 B

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -85,7 +85,7 @@
#set text(size: 12pt, weight: "regular")
#outline(
title: "Chapter outline",
title: none,
indent: true,
target: heading
.where(level: 1)

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -29,7 +29,7 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
#set text(8pt)
#quote(attribution: <tolkien54>)[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: <tolkien54>)[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: <tolkien54>)[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: <tolkien54>)[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 ---

View File

@ -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)