mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Merge 23ef479a730057529773eb8190805e5c48169a84 into 9b09146a6b5e936966ed7ee73bce9dd2df3810ae
This commit is contained in:
commit
ffac381888
@ -14,7 +14,7 @@ use typst_library::introspection::{
|
|||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem,
|
Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem,
|
||||||
Fr, Fragment, Frame, PagebreakElem, PlaceElem, PlacementScope, Ratio, Region,
|
Fr, Fragment, Frame, PagebreakElem, PlaceElem, PlacementScope, Ratio, Region,
|
||||||
Regions, Rel, Size, Sizing, Spacing, VElem,
|
Regions, Rel, Size, Sizing, Spacing, Sticky, VElem,
|
||||||
};
|
};
|
||||||
use typst_library::model::ParElem;
|
use typst_library::model::ParElem;
|
||||||
use typst_library::routines::{Pair, Routines};
|
use typst_library::routines::{Pair, Routines};
|
||||||
@ -375,7 +375,7 @@ pub struct LineChild {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SingleChild<'a> {
|
pub struct SingleChild<'a> {
|
||||||
pub align: Axes<FixedAlignment>,
|
pub align: Axes<FixedAlignment>,
|
||||||
pub sticky: bool,
|
pub sticky: Option<Sticky>,
|
||||||
pub alone: bool,
|
pub alone: bool,
|
||||||
pub fr: Option<Fr>,
|
pub fr: Option<Fr>,
|
||||||
elem: &'a Packed<BlockElem>,
|
elem: &'a Packed<BlockElem>,
|
||||||
@ -441,7 +441,7 @@ fn layout_single_impl(
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MultiChild<'a> {
|
pub struct MultiChild<'a> {
|
||||||
pub align: Axes<FixedAlignment>,
|
pub align: Axes<FixedAlignment>,
|
||||||
pub sticky: bool,
|
pub sticky: Option<Sticky>,
|
||||||
alone: bool,
|
alone: bool,
|
||||||
elem: &'a Packed<BlockElem>,
|
elem: &'a Packed<BlockElem>,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use typst_library::introspection::Tag;
|
use typst_library::introspection::Tag;
|
||||||
use typst_library::layout::{
|
use typst_library::layout::{
|
||||||
Abs, Axes, FixedAlignment, Fr, Frame, FrameItem, Point, Region, Regions, Rel, Size,
|
Abs, Axes, FixedAlignment, Fr, Frame, FrameItem, Point, Region, Regions, Rel, Size,
|
||||||
|
Sticky,
|
||||||
};
|
};
|
||||||
use typst_utils::Numeric;
|
use typst_utils::Numeric;
|
||||||
|
|
||||||
@ -244,7 +245,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
return Err(Stop::Finish(false));
|
return Err(Stop::Finish(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.frame(line.frame.clone(), line.align, false, false)
|
self.frame(line.frame.clone(), line.align, None, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes an unbreakable block.
|
/// Processes an unbreakable block.
|
||||||
@ -307,7 +308,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
// Lay out the spilled remains.
|
// Lay out the spilled remains.
|
||||||
let align = spill.align();
|
let align = spill.align();
|
||||||
let (frame, spill) = spill.layout(self.composer.engine, self.regions)?;
|
let (frame, spill) = spill.layout(self.composer.engine, self.regions)?;
|
||||||
self.frame(frame, align, false, true)?;
|
self.frame(frame, align, None, true)?;
|
||||||
|
|
||||||
// If there's still more, save it into the `spill` and finish the
|
// If there's still more, save it into the `spill` and finish the
|
||||||
// region.
|
// region.
|
||||||
@ -324,10 +325,28 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
align: Axes<FixedAlignment>,
|
align: Axes<FixedAlignment>,
|
||||||
sticky: bool,
|
sticky: Option<Sticky>,
|
||||||
breakable: bool,
|
breakable: bool,
|
||||||
) -> FlowResult<()> {
|
) -> FlowResult<()> {
|
||||||
if sticky {
|
// Check if the successor frame has sticky: above
|
||||||
|
let has_sticky_successor = self
|
||||||
|
.composer
|
||||||
|
.work
|
||||||
|
.children
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.find_map(|child| match child {
|
||||||
|
Child::Single(single) => Some(
|
||||||
|
single.sticky.as_ref().map(|s| s.is_sticky_above()).unwrap_or(false),
|
||||||
|
),
|
||||||
|
Child::Multi(multi) => Some(
|
||||||
|
multi.sticky.as_ref().map(|s| s.is_sticky_above()).unwrap_or(false),
|
||||||
|
),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let mut stick_to_successor = || {
|
||||||
// If the frame is sticky and we haven't remembered a preceding
|
// If the frame is sticky and we haven't remembered a preceding
|
||||||
// sticky element, make a checkpoint which we can restore should we
|
// sticky element, make a checkpoint which we can restore should we
|
||||||
// end on this sticky element.
|
// end on this sticky element.
|
||||||
@ -350,13 +369,23 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
{
|
{
|
||||||
self.sticky = Some(self.snapshot());
|
self.sticky = Some(self.snapshot());
|
||||||
}
|
}
|
||||||
} else if !frame.is_empty() {
|
};
|
||||||
// If the frame isn't sticky, we can forget a previous snapshot. We
|
|
||||||
// interrupt a group of sticky blocks, if there was one, so we reset
|
match sticky {
|
||||||
// the saved stickable check for the next group of sticky blocks.
|
Some(Sticky::Below) => {
|
||||||
|
stick_to_successor();
|
||||||
|
}
|
||||||
|
_ if has_sticky_successor => {
|
||||||
|
stick_to_successor();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Only clear the snapshot if this frame isn't empty
|
||||||
|
if !frame.is_empty() {
|
||||||
self.sticky = None;
|
self.sticky = None;
|
||||||
self.stickable = None;
|
self.stickable = None;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle footnotes.
|
// Handle footnotes.
|
||||||
self.composer.footnotes(
|
self.composer.footnotes(
|
||||||
@ -443,9 +472,15 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
|||||||
// the flow, restore the saved checkpoint to move the sticky
|
// the flow, restore the saved checkpoint to move the sticky
|
||||||
// suffix to the next region.
|
// suffix to the next region.
|
||||||
if let Some(snapshot) = self.sticky.take() {
|
if let Some(snapshot) = self.sticky.take() {
|
||||||
|
// Only restore snapshot if there's no spill
|
||||||
|
// If a sticky breakable element can be spilled to the following page,
|
||||||
|
// it's fine to put some of it on this page, since the next page will still
|
||||||
|
// have the next element on the same page as *some* of the spilled content.
|
||||||
|
if self.composer.work.spill.is_none() {
|
||||||
self.restore(snapshot)
|
self.restore(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.trim_spacing();
|
self.trim_spacing();
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Smart,
|
cast, elem, Args, AutoValue, Cast, Construct, Content, NativeElement, Packed, Smart,
|
||||||
StyleChain, Value,
|
StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
@ -179,6 +179,27 @@ pub enum InlineItem {
|
|||||||
Frame(Frame),
|
Frame(Frame),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Defines how a block sticks to adjacent content.
|
||||||
|
#[derive(Debug, Cast, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Sticky {
|
||||||
|
/// Block sticks to the content below it.
|
||||||
|
Below,
|
||||||
|
/// Makes the block above stick to this block.
|
||||||
|
Above,
|
||||||
|
/// Makes the block above and below stick to this block.
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sticky {
|
||||||
|
pub fn is_sticky_above(self) -> bool {
|
||||||
|
matches!(self, Self::Above | Self::Both)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_sticky_below(self) -> bool {
|
||||||
|
matches!(self, Self::Below | Self::Both)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A block-level container.
|
/// A block-level container.
|
||||||
///
|
///
|
||||||
/// Such a container can be used to separate content, size it, and give it a
|
/// Such a container can be used to separate content, size it, and give it a
|
||||||
@ -338,23 +359,29 @@ pub struct BlockElem {
|
|||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub clip: bool,
|
pub clip: bool,
|
||||||
|
|
||||||
/// Whether this block must stick to the following one, with no break in
|
/// If and how this block must stick to the blocks surrounding it, with no
|
||||||
/// between.
|
/// break in between.
|
||||||
///
|
///
|
||||||
/// This is, by default, set on heading blocks to prevent orphaned headings
|
/// This is, by default, set to "below" on heading blocks to prevent
|
||||||
/// at the bottom of the page.
|
/// orphaned headings at the bottom of the page.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// >>> #set page(height: 140pt)
|
/// >>> #set page(height: 140pt)
|
||||||
/// // Disable stickiness of headings.
|
/// // Disable stickiness of headings.
|
||||||
/// #show heading: set block(sticky: false)
|
/// #show heading: set block(sticky: none)
|
||||||
/// #lorem(20)
|
/// #lorem(20)
|
||||||
///
|
///
|
||||||
/// = Chapter
|
/// = Chapter
|
||||||
/// #lorem(10)
|
/// #lorem(10)
|
||||||
|
///
|
||||||
|
/// #table(
|
||||||
|
/// columns: 2,
|
||||||
|
/// [A], [B],
|
||||||
|
/// [C], [D],
|
||||||
|
/// )
|
||||||
|
/// #block(sticky: "above")[The above table shows that...]
|
||||||
/// ```
|
/// ```
|
||||||
#[default(false)]
|
pub sticky: Option<Sticky>,
|
||||||
pub sticky: bool,
|
|
||||||
|
|
||||||
/// The contents of the block.
|
/// The contents of the block.
|
||||||
#[positional]
|
#[positional]
|
||||||
|
@ -13,7 +13,9 @@ use crate::html::{attr, tag, HtmlElem};
|
|||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
|
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
|
||||||
};
|
};
|
||||||
use crate::layout::{Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region, Sides};
|
use crate::layout::{
|
||||||
|
Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region, Sides, Sticky,
|
||||||
|
};
|
||||||
use crate::model::{Numbering, Outlinable, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, Refable, Supplement};
|
||||||
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
||||||
|
|
||||||
@ -323,7 +325,7 @@ impl ShowSet for Packed<HeadingElem> {
|
|||||||
out.set(TextElem::set_weight(FontWeight::BOLD));
|
out.set(TextElem::set_weight(FontWeight::BOLD));
|
||||||
out.set(BlockElem::set_above(Smart::Custom(above.into())));
|
out.set(BlockElem::set_above(Smart::Custom(above.into())));
|
||||||
out.set(BlockElem::set_below(Smart::Custom(below.into())));
|
out.set(BlockElem::set_below(Smart::Custom(below.into())));
|
||||||
out.set(BlockElem::set_sticky(true));
|
out.set(BlockElem::set_sticky(Some(Sticky::Below)));
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
tests/ref/block-sticky-above.png
Normal file
BIN
tests/ref/block-sticky-above.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -155,17 +155,17 @@ Paragraph
|
|||||||
--- block-sticky ---
|
--- block-sticky ---
|
||||||
#set page(height: 100pt)
|
#set page(height: 100pt)
|
||||||
#lines(3)
|
#lines(3)
|
||||||
#block(sticky: true)[D]
|
#block(sticky: "below")[D]
|
||||||
#block(sticky: true)[E]
|
#block(sticky: "below")[E]
|
||||||
F
|
F
|
||||||
|
|
||||||
--- block-sticky-alone ---
|
--- block-sticky-alone ---
|
||||||
#set page(height: 50pt)
|
#set page(height: 50pt)
|
||||||
#block(sticky: true)[A]
|
#block(sticky: "below")[A]
|
||||||
|
|
||||||
--- block-sticky-many ---
|
--- block-sticky-many ---
|
||||||
#set page(height: 80pt)
|
#set page(height: 80pt)
|
||||||
#set block(sticky: true)
|
#set block(breakable: false, sticky: "below")
|
||||||
#block[A]
|
#block[A]
|
||||||
#block[B]
|
#block[B]
|
||||||
#block[C]
|
#block[C]
|
||||||
@ -176,16 +176,22 @@ E
|
|||||||
|
|
||||||
--- block-sticky-colbreak ---
|
--- block-sticky-colbreak ---
|
||||||
A
|
A
|
||||||
#block(sticky: true)[B]
|
#block(sticky: "below")[B]
|
||||||
#colbreak()
|
#colbreak()
|
||||||
C
|
C
|
||||||
|
|
||||||
--- block-sticky-breakable ---
|
--- block-sticky-breakable ---
|
||||||
// Ensure that sticky blocks are still breakable.
|
// Ensure that sticky blocks are still breakable.
|
||||||
#set page(height: 60pt)
|
#set page(height: 60pt)
|
||||||
#block(sticky: true, lines(4))
|
#block(sticky: "below", lines(4))
|
||||||
E
|
E
|
||||||
|
|
||||||
|
--- block-sticky-above ---
|
||||||
|
#set page(height: 50pt)
|
||||||
|
#block(height: 15pt)[A]
|
||||||
|
#block(height: 5pt, breakable: false)[B]
|
||||||
|
#block(height: 5pt, sticky: "above")[C]
|
||||||
|
|
||||||
--- box-clip-rect ---
|
--- box-clip-rect ---
|
||||||
// Test box clipping with a rectangle
|
// Test box clipping with a rectangle
|
||||||
Hello #box(width: 1em, height: 1em, clip: false)[#rect(width: 3em, height: 3em, fill: red)]
|
Hello #box(width: 1em, height: 1em, clip: false)[#rect(width: 3em, height: 3em, fill: red)]
|
||||||
@ -291,7 +297,7 @@ Paragraph
|
|||||||
#set page(height: 3cm)
|
#set page(height: 3cm)
|
||||||
#v(1.6cm)
|
#v(1.6cm)
|
||||||
#block(height: 2cm, breakable: true)[
|
#block(height: 2cm, breakable: true)[
|
||||||
#block(sticky: true)[*A*]
|
#block(sticky: "below")[*A*]
|
||||||
|
|
||||||
b
|
b
|
||||||
]
|
]
|
||||||
@ -300,7 +306,7 @@ Paragraph
|
|||||||
#set page(height: 3cm)
|
#set page(height: 3cm)
|
||||||
#v(2cm)
|
#v(2cm)
|
||||||
|
|
||||||
#block(sticky: true)[*A*]
|
#block(sticky: "below")[*A*]
|
||||||
|
|
||||||
b
|
b
|
||||||
|
|
||||||
@ -308,7 +314,7 @@ b
|
|||||||
#set page(height: 3cm)
|
#set page(height: 3cm)
|
||||||
#v(2cm, weak: true)
|
#v(2cm, weak: true)
|
||||||
|
|
||||||
#block(sticky: true)[*A*]
|
#block(sticky: "below")[*A*]
|
||||||
|
|
||||||
b
|
b
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user