summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/typst/src/layout/grid/mod.rs8
-rw-r--r--crates/typst/src/model/figure.rs49
-rw-r--r--tests/ref/figure-caption-repeat-bottom.pngbin0 -> 1750 bytes
-rw-r--r--tests/ref/figure-caption-repeat-top.pngbin0 -> 1290 bytes
-rw-r--r--tests/suite/model/figure.typ28
5 files changed, 78 insertions, 7 deletions
diff --git a/crates/typst/src/layout/grid/mod.rs b/crates/typst/src/layout/grid/mod.rs
index ec0504f3..b57d6744 100644
--- a/crates/typst/src/layout/grid/mod.rs
+++ b/crates/typst/src/layout/grid/mod.rs
@@ -413,11 +413,17 @@ pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
cast! {
TrackSizings,
self => self.0.into_value(),
- sizing: Sizing => Self(smallvec![sizing]),
+ sizing: Sizing => Self::from(sizing),
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
values: Array => Self(values.into_iter().map(Value::cast).collect::<HintedStrResult<_>>()?),
}
+impl From<Sizing> for TrackSizings {
+ fn from(sizing: Sizing) -> Self {
+ Self(smallvec![sizing])
+ }
+}
+
/// Any child of a grid element.
#[derive(Debug, PartialEq, Clone, Hash)]
pub enum GridChild {
diff --git a/crates/typst/src/model/figure.rs b/crates/typst/src/model/figure.rs
index 1d3c9c95..9ed59b02 100644
--- a/crates/typst/src/model/figure.rs
+++ b/crates/typst/src/model/figure.rs
@@ -14,8 +14,9 @@ use crate::introspection::{
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
};
use crate::layout::{
- AlignElem, Alignment, BlockBody, BlockElem, Em, HAlignment, Length, OuterVAlignment,
- PlaceElem, PlacementScope, VAlignment, VElem,
+ AlignElem, Alignment, BlockBody, BlockElem, Em, GridCell, GridChild, GridElem,
+ GridFooter, GridHeader, GridItem, HAlignment, Length, OuterVAlignment, PlaceElem,
+ PlacementScope, Sizing, TrackSizings, VAlignment, VElem,
};
use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
use crate::text::{Lang, Region, TextElem};
@@ -330,10 +331,27 @@ impl Show for Packed<FigureElem> {
// Build the caption, if any.
if let Some(caption) = self.caption(styles) {
- let v = VElem::new(self.gap(styles).into()).with_weak(true).pack();
- realized = match caption.position(styles) {
- OuterVAlignment::Top => caption.pack() + v + realized,
- OuterVAlignment::Bottom => realized + v + caption.pack(),
+ let gap = self.gap(styles);
+ let v = || VElem::new(gap.into()).with_weak(true).pack();
+ realized = match (caption.repeat(styles), caption.position(styles)) {
+ (true, OuterVAlignment::Top) => GridElem::new(vec![
+ GridChild::Header(Packed::new(GridHeader::new(vec![
+ GridItem::Cell(Packed::new(GridCell::new(caption.pack()))),
+ ]))),
+ GridChild::Item(GridItem::Cell(Packed::new(GridCell::new(realized)))),
+ ])
+ .with_row_gutter(TrackSizings::from(Sizing::from(gap)))
+ .pack(),
+ (true, OuterVAlignment::Bottom) => GridElem::new(vec![
+ GridChild::Item(GridItem::Cell(Packed::new(GridCell::new(realized)))),
+ GridChild::Footer(Packed::new(GridFooter::new(vec![
+ GridItem::Cell(Packed::new(GridCell::new(caption.pack()))),
+ ]))),
+ ])
+ .with_row_gutter(TrackSizings::from(Sizing::from(gap)))
+ .pack(),
+ (false, OuterVAlignment::Top) => caption.pack() + v() + realized,
+ (false, OuterVAlignment::Bottom) => realized + v() + caption.pack(),
};
}
@@ -497,6 +515,25 @@ pub struct FigureCaption {
#[default(OuterVAlignment::Bottom)]
pub position: OuterVAlignment,
+ /// Whether the figure caption should be repeated if the figure breaks.
+ ///
+ /// ```example
+ /// #show figure.where(kind: table): set block(breakable: true)
+ /// #set page(height: 7em)
+ /// #figure(
+ /// table(
+ /// columns: 3,
+ /// [A], [B], [C],
+ /// [D], [E], [F],
+ /// [G], [H], [I],
+ /// [J], [K], [L]
+ /// ),
+ /// caption: figure.caption(repeat: true)[A nice table.]
+ /// )
+ /// ```
+ #[default(false)]
+ pub repeat: bool,
+
/// The separator which will appear between the number and body.
///
/// If set to `{auto}`, the separator will be adapted to the current
diff --git a/tests/ref/figure-caption-repeat-bottom.png b/tests/ref/figure-caption-repeat-bottom.png
new file mode 100644
index 00000000..9e72f07d
--- /dev/null
+++ b/tests/ref/figure-caption-repeat-bottom.png
Binary files differ
diff --git a/tests/ref/figure-caption-repeat-top.png b/tests/ref/figure-caption-repeat-top.png
new file mode 100644
index 00000000..a9fe48c8
--- /dev/null
+++ b/tests/ref/figure-caption-repeat-top.png
Binary files differ
diff --git a/tests/suite/model/figure.typ b/tests/suite/model/figure.typ
index fbd0ab29..0a073160 100644
--- a/tests/suite/model/figure.typ
+++ b/tests/suite/model/figure.typ
@@ -219,6 +219,34 @@ We can clearly see that @fig-cylinder and
// Error: 31-38 expected `top` or `bottom`, found horizon
#set figure.caption(position: horizon)
+--- figure-caption-repeat-bottom ---
+#show figure.where(kind: table): set block(breakable: true)
+#set page(height: 7em)
+#figure(
+ table(
+ columns: 3,
+ [A], [B], [C],
+ [D], [E], [F],
+ [G], [H], [I],
+ [J], [K], [L]
+ ),
+ caption: figure.caption(repeat: true)[A nice table.]
+)
+
+--- figure-caption-repeat-top ---
+#show figure.where(kind: table): set block(breakable: true)
+#set page(height: 7em)
+#figure(
+ table(
+ columns: 3,
+ [A], [B], [C],
+ [D], [E], [F],
+ [G], [H], [I],
+ [J], [K], [L]
+ ),
+ caption: figure.caption(position: top, repeat: true)[A nice table.]
+)
+
--- figure-localization-fr ---
// Test French
#set text(lang: "fr")