mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +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::{
|
||||
Abs, AlignElem, Alignment, Axes, BlockElem, ColbreakElem, FixedAlignment, FlushElem,
|
||||
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::routines::{Pair, Routines};
|
||||
@ -375,7 +375,7 @@ pub struct LineChild {
|
||||
#[derive(Debug)]
|
||||
pub struct SingleChild<'a> {
|
||||
pub align: Axes<FixedAlignment>,
|
||||
pub sticky: bool,
|
||||
pub sticky: Option<Sticky>,
|
||||
pub alone: bool,
|
||||
pub fr: Option<Fr>,
|
||||
elem: &'a Packed<BlockElem>,
|
||||
@ -441,7 +441,7 @@ fn layout_single_impl(
|
||||
#[derive(Debug)]
|
||||
pub struct MultiChild<'a> {
|
||||
pub align: Axes<FixedAlignment>,
|
||||
pub sticky: bool,
|
||||
pub sticky: Option<Sticky>,
|
||||
alone: bool,
|
||||
elem: &'a Packed<BlockElem>,
|
||||
styles: StyleChain<'a>,
|
||||
|
@ -1,6 +1,7 @@
|
||||
use typst_library::introspection::Tag;
|
||||
use typst_library::layout::{
|
||||
Abs, Axes, FixedAlignment, Fr, Frame, FrameItem, Point, Region, Regions, Rel, Size,
|
||||
Sticky,
|
||||
};
|
||||
use typst_utils::Numeric;
|
||||
|
||||
@ -244,7 +245,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
||||
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.
|
||||
@ -307,7 +308,7 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
||||
// Lay out the spilled remains.
|
||||
let align = spill.align();
|
||||
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
|
||||
// region.
|
||||
@ -324,10 +325,28 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
||||
&mut self,
|
||||
frame: Frame,
|
||||
align: Axes<FixedAlignment>,
|
||||
sticky: bool,
|
||||
sticky: Option<Sticky>,
|
||||
breakable: bool,
|
||||
) -> 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
|
||||
// sticky element, make a checkpoint which we can restore should we
|
||||
// end on this sticky element.
|
||||
@ -350,12 +369,22 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
||||
{
|
||||
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
|
||||
// the saved stickable check for the next group of sticky blocks.
|
||||
self.sticky = None;
|
||||
self.stickable = None;
|
||||
};
|
||||
|
||||
match sticky {
|
||||
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.stickable = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle footnotes.
|
||||
@ -443,7 +472,13 @@ impl<'a, 'b> Distributor<'a, 'b, '_, '_, '_> {
|
||||
// the flow, restore the saved checkpoint to move the sticky
|
||||
// suffix to the next region.
|
||||
if let Some(snapshot) = self.sticky.take() {
|
||||
self.restore(snapshot)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::diag::{bail, SourceResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
cast, elem, Args, AutoValue, Construct, Content, NativeElement, Packed, Smart,
|
||||
cast, elem, Args, AutoValue, Cast, Construct, Content, NativeElement, Packed, Smart,
|
||||
StyleChain, Value,
|
||||
};
|
||||
use crate::introspection::Locator;
|
||||
@ -179,6 +179,27 @@ pub enum InlineItem {
|
||||
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.
|
||||
///
|
||||
/// Such a container can be used to separate content, size it, and give it a
|
||||
@ -338,23 +359,29 @@ pub struct BlockElem {
|
||||
#[default(false)]
|
||||
pub clip: bool,
|
||||
|
||||
/// Whether this block must stick to the following one, with no break in
|
||||
/// between.
|
||||
/// If and how this block must stick to the blocks surrounding it, with no
|
||||
/// break in between.
|
||||
///
|
||||
/// This is, by default, set on heading blocks to prevent orphaned headings
|
||||
/// at the bottom of the page.
|
||||
/// This is, by default, set to "below" on heading blocks to prevent
|
||||
/// orphaned headings at the bottom of the page.
|
||||
///
|
||||
/// ```example
|
||||
/// >>> #set page(height: 140pt)
|
||||
/// // Disable stickiness of headings.
|
||||
/// #show heading: set block(sticky: false)
|
||||
/// #show heading: set block(sticky: none)
|
||||
/// #lorem(20)
|
||||
///
|
||||
/// = Chapter
|
||||
/// #lorem(10)
|
||||
///
|
||||
/// #table(
|
||||
/// columns: 2,
|
||||
/// [A], [B],
|
||||
/// [C], [D],
|
||||
/// )
|
||||
/// #block(sticky: "above")[The above table shows that...]
|
||||
/// ```
|
||||
#[default(false)]
|
||||
pub sticky: bool,
|
||||
pub sticky: Option<Sticky>,
|
||||
|
||||
/// The contents of the block.
|
||||
#[positional]
|
||||
|
@ -13,7 +13,9 @@ use crate::html::{attr, tag, HtmlElem};
|
||||
use crate::introspection::{
|
||||
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::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
||||
|
||||
@ -323,7 +325,7 @@ impl ShowSet for Packed<HeadingElem> {
|
||||
out.set(TextElem::set_weight(FontWeight::BOLD));
|
||||
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.set(BlockElem::set_sticky(Some(Sticky::Below)));
|
||||
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 ---
|
||||
#set page(height: 100pt)
|
||||
#lines(3)
|
||||
#block(sticky: true)[D]
|
||||
#block(sticky: true)[E]
|
||||
#block(sticky: "below")[D]
|
||||
#block(sticky: "below")[E]
|
||||
F
|
||||
|
||||
--- block-sticky-alone ---
|
||||
#set page(height: 50pt)
|
||||
#block(sticky: true)[A]
|
||||
#block(sticky: "below")[A]
|
||||
|
||||
--- block-sticky-many ---
|
||||
#set page(height: 80pt)
|
||||
#set block(sticky: true)
|
||||
#set block(breakable: false, sticky: "below")
|
||||
#block[A]
|
||||
#block[B]
|
||||
#block[C]
|
||||
@ -176,16 +176,22 @@ E
|
||||
|
||||
--- block-sticky-colbreak ---
|
||||
A
|
||||
#block(sticky: true)[B]
|
||||
#block(sticky: "below")[B]
|
||||
#colbreak()
|
||||
C
|
||||
|
||||
--- block-sticky-breakable ---
|
||||
// Ensure that sticky blocks are still breakable.
|
||||
#set page(height: 60pt)
|
||||
#block(sticky: true, lines(4))
|
||||
#block(sticky: "below", lines(4))
|
||||
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 ---
|
||||
// Test box clipping with a rectangle
|
||||
Hello #box(width: 1em, height: 1em, clip: false)[#rect(width: 3em, height: 3em, fill: red)]
|
||||
@ -291,7 +297,7 @@ Paragraph
|
||||
#set page(height: 3cm)
|
||||
#v(1.6cm)
|
||||
#block(height: 2cm, breakable: true)[
|
||||
#block(sticky: true)[*A*]
|
||||
#block(sticky: "below")[*A*]
|
||||
|
||||
b
|
||||
]
|
||||
@ -300,7 +306,7 @@ Paragraph
|
||||
#set page(height: 3cm)
|
||||
#v(2cm)
|
||||
|
||||
#block(sticky: true)[*A*]
|
||||
#block(sticky: "below")[*A*]
|
||||
|
||||
b
|
||||
|
||||
@ -308,7 +314,7 @@ b
|
||||
#set page(height: 3cm)
|
||||
#v(2cm, weak: true)
|
||||
|
||||
#block(sticky: true)[*A*]
|
||||
#block(sticky: "below")[*A*]
|
||||
|
||||
b
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user