From 976abdfe7dc08ae42ee87e5c2d4ff46ebe172dd1 Mon Sep 17 00:00:00 2001 From: MALO <57839069+MDLC01@users.noreply.github.com> Date: Tue, 12 Sep 2023 14:47:36 +0200 Subject: [PATCH] Add `figure.caption` element (#1704) Co-authored-by: Laurenz --- crates/typst-library/src/meta/figure.rs | 280 +++++++++++++++++------- docs/changelog.md | 4 +- tests/ref/meta/figure-caption.png | Bin 0 -> 9192 bytes tests/typ/meta/figure-caption.typ | 56 +++++ tests/typ/meta/figure.typ | 2 +- tests/typ/meta/query-figure.typ | 2 +- 6 files changed, 265 insertions(+), 79 deletions(-) create mode 100644 tests/ref/meta/figure-caption.png create mode 100644 tests/typ/meta/figure-caption.typ diff --git a/crates/typst-library/src/meta/figure.rs b/crates/typst-library/src/meta/figure.rs index 6e95dce7b..4aebdda6b 100644 --- a/crates/typst-library/src/meta/figure.rs +++ b/crates/typst-library/src/meta/figure.rs @@ -11,9 +11,9 @@ use crate::visualize::ImageElem; /// A figure with an optional caption. /// -/// Automatically detects its contents to select the correct counting track. -/// For example, figures containing images will be numbered separately from -/// figures containing tables. +/// Automatically detects its contents to select the correct counting track. For +/// example, figures containing images will be numbered separately from figures +/// containing tables. /// /// # Examples /// The example below shows a basic figure with an image: @@ -44,36 +44,51 @@ use crate::visualize::ImageElem; /// This behaviour can be overridden by explicitly specifying the figure's /// `kind`. All figures of the same kind share a common counter. /// -/// # Modifying the appearance { #modifying-appearance } -/// You can completely customize the look of your figures with a [show -/// rule]($styling/#show-rules). In the example below, we show the figure's -/// caption above its body and display its supplement and counter after the -/// caption. +/// # Figure behaviour +/// By default, figures are placed within the flow of content. To make them +/// float to the top or bottom of the page, you can use the +/// [`placement`]($figure.placement) argument. +/// +/// If your figure is too large and its contents are breakable across pages +/// (e.g. if it contains a large table), then you can make the figure itself +/// breakable across pages as well with this show rule: +/// ```typ +/// #show figure: set block(breakable: true) +/// ``` +/// +/// See the [block]($block.breakable) documentation for more information about +/// breakable and non-breakable blocks. +/// +/// # Caption customization +/// You can modify the apperance of the figure's caption with its associated +/// [`caption`]($figure.caption) function. In the example below, we emphasize +/// all captions: /// /// ```example -/// #show figure: it => align(center)[ -/// #it.caption | -/// #emph[ -/// #it.supplement -/// #it.counter.display(it.numbering) -/// ] -/// #v(10pt, weak: true) -/// #it.body -/// ] +/// #show figure.caption: emph /// /// #figure( -/// image("molecular.jpg", width: 80%), -/// caption: [ -/// The molecular testing pipeline. -/// ], +/// rect[Hello], +/// caption: [I am emphasized!], /// ) /// ``` /// -/// If your figure is too large and its contents are breakable across pages -/// (e.g. if it contains a large table), then you can make the figure breakable -/// across pages as well by using `[#show figure: set block(breakable: true)]` -/// (see the [block]($block) documentation for more information). -#[elem(Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)] +/// By using a [`where`]($function.where) selector, we can scope such rules to +/// specific kinds of figures. For example, to position the caption above +/// tables, but keep it below for all other kinds of figures, we could write the +/// following show-set rule: +/// +/// ```example +/// #show figure.where( +/// kind: table +/// ): set figure.caption(position: top) +/// +/// #figure( +/// table(columns: 2)[A][B][C][D], +/// caption: [I'm up here], +/// ) +/// ``` +#[elem(scope, Locatable, Synthesize, Count, Show, Finalize, Refable, Outlinable)] pub struct FigureElem { /// The content of the figure. Often, an [image]($image). #[required] @@ -88,6 +103,10 @@ pub struct FigureElem { /// - `{top}`: The figure floats to the top of the page. /// - `{bottom}`: The figure floats to the bottom of the page. /// + /// The gap between the main flow content and the floating figure is + /// controlled by the [`clearance`]($place.clearance) argument on the + /// `place` function. + /// /// ```example /// #set page(height: 200pt) /// @@ -102,33 +121,7 @@ pub struct FigureElem { pub placement: Option>, /// The figure's caption. - pub caption: Option, - - /// The caption's position. Either `{top}` or `{bottom}`. - /// - /// ```example - /// #figure( - /// table(columns: 2)[A][B], - /// caption: [I'm up here], - /// caption-pos: top, - /// ) - /// - /// #figure( - /// table(columns: 2)[A][B], - /// caption: [I'm down here], - /// ) - /// ``` - #[default(VAlign::Bottom)] - #[parse({ - let option: Option> = args.named("caption-pos")?; - if let Some(Spanned { v: align, span }) = option { - if align == VAlign::Horizon { - bail!(span, "expected `top` or `bottom`"); - } - } - option.map(|spanned| spanned.v) - })] - pub caption_pos: VAlign, + pub caption: Option, /// The kind of figure this is. /// @@ -204,6 +197,12 @@ pub struct FigureElem { pub counter: Option, } +#[scope] +impl FigureElem { + #[elem] + type FigureCaption; +} + impl Synthesize for FigureElem { fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { let numbering = self.numbering(styles); @@ -238,9 +237,9 @@ impl Synthesize for FigureElem { bail!(self.span(), "please specify the figure's supplement") } - name.unwrap_or_default() + Some(name.unwrap_or_default()) } - Smart::Custom(None) => Content::empty(), + Smart::Custom(None) => None, Smart::Custom(Some(supplement)) => { // Resolve the supplement with the first descendant of the kind or // just the body, if none was found. @@ -252,7 +251,7 @@ impl Synthesize for FigureElem { }; let target = descendant.unwrap_or_else(|| self.body()); - supplement.resolve(vt, [target])? + Some(supplement.resolve(vt, [target])?) } }; @@ -264,11 +263,20 @@ impl Synthesize for FigureElem { }), ))); + // Fill the figure's caption. + let mut caption = self.caption(styles); + if let Some(caption) = &mut caption { + caption.push_kind(kind.clone()); + caption.push_supplement(supplement.clone()); + caption.push_numbering(numbering.clone()); + caption.push_counter(Some(counter.clone())); + caption.push_location(self.0.location()); + } + self.push_placement(self.placement(styles)); - self.push_caption_pos(self.caption_pos(styles)); - self.push_caption(self.caption(styles)); + self.push_caption(caption); self.push_kind(Smart::Custom(kind)); - self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement)))); + self.push_supplement(Smart::Custom(supplement.map(Supplement::Content))); self.push_numbering(numbering); self.push_outlined(self.outlined(styles)); self.push_counter(Some(counter)); @@ -279,18 +287,18 @@ impl Synthesize for FigureElem { impl Show for FigureElem { #[tracing::instrument(name = "FigureElem::show", skip_all)] - fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult { + fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut realized = self.body(); // Build the caption, if any. - if let Some(caption) = self.full_caption(vt)? { + if let Some(caption) = self.caption(styles) { let v = VElem::weak(self.gap(styles).into()).pack(); - realized = if self.caption_pos(styles) == VAlign::Bottom { - realized + v + caption + realized = if caption.position(styles) == VAlign::Bottom { + realized + v + caption.pack() } else { - caption + v + realized - } - }; + caption.pack() + v + realized + }; + } // Wrap the contents in a block. realized = BlockElem::new() @@ -351,14 +359,9 @@ impl Outlinable for FigureElem { return Ok(None); } - self.full_caption(vt) - } -} - -impl FigureElem { - /// Builds the full caption for the figure (with supplement and numbering). - pub fn full_caption(&self, vt: &mut Vt) -> SourceResult> { - let Some(mut caption) = self.caption(StyleChain::default()) else { + let Some(mut caption) = + self.caption(StyleChain::default()).map(|caption| caption.body()) + else { return Ok(None); }; @@ -375,7 +378,7 @@ impl FigureElem { let numbers = counter.at(vt, location)?.display(vt, &numbering)?; if !supplement.is_empty() { - supplement += TextElem::packed("\u{a0}"); + supplement += TextElem::packed('\u{a0}'); } caption = supplement + numbers + TextElem::packed(": ") + caption; @@ -385,6 +388,133 @@ impl FigureElem { } } +/// The caption of a figure. This element can be used in set and show rules to +/// customize the appearance of captions for all figures or figures of a +/// specific kind. +/// +/// In addition to its `pos` and `body`, the `caption` also provides the +/// figure's `kind`, `supplement`, `counter`, `numbering`, and `location` as +/// fields. These parts can be used in [`where`]($function.where) selectors and +/// show rules to build a completely custom caption. +/// +/// ```example +/// #show figure.caption: emph +/// +/// #figure( +/// rect[Hello], +/// caption: [A rectangle], +/// ) +/// ``` +#[elem(name = "caption", Synthesize, Show)] +pub struct FigureCaption { + /// The caption's position in the figure. Either `{top}` or `{bottom}`. + /// + /// ```example + /// #show figure.where( + /// kind: table + /// ): set figure.caption(position: top) + /// + /// #figure( + /// table(columns: 2)[A][B], + /// caption: [I'm up here], + /// ) + /// + /// #figure( + /// rect[Hi], + /// caption: [I'm down here], + /// ) + /// + /// #figure( + /// table(columns: 2)[A][B], + /// caption: figure.caption( + /// position: bottom, + /// [I'm down here too!] + /// ) + /// ) + /// ``` + #[default(VAlign::Bottom)] + #[parse({ + let option: Option> = args.named("position")?; + if let Some(Spanned { v: align, span }) = option { + if align == VAlign::Horizon { + bail!(span, "expected `top` or `bottom`"); + } + } + option.map(|spanned| spanned.v) + })] + pub position: VAlign, + + /// The caption's body. + /// + /// Can be used alongside `kind`, `supplement`, `counter`, `numbering`, and + /// `location` to completely customize the caption. + /// + /// ```example + /// #show figure.caption: it => [ + /// #underline(it.body) | + /// #it.supplement #it.counter.display(it.numbering) + /// ] + /// + /// #figure( + /// rect[Hello], + /// caption: [A rectangle], + /// ) + /// ``` + #[required] + pub body: Content, + + /// The figure's supplement. + #[synthesized] + pub kind: FigureKind, + + /// The figure's supplement. + #[synthesized] + pub supplement: Option, + + /// How to number the figure. + #[synthesized] + pub numbering: Option, + + /// The counter for the figure. + #[synthesized] + pub counter: Option, + + /// The figure's location. + #[synthesized] + pub location: Option, +} + +impl Synthesize for FigureCaption { + fn synthesize(&mut self, _: &mut Vt, styles: StyleChain) -> SourceResult<()> { + self.push_position(self.position(styles)); + Ok(()) + } +} + +impl Show for FigureCaption { + #[tracing::instrument(name = "FigureCaption::show", skip_all)] + fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult { + let mut realized = self.body(); + + if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = + (self.supplement(), self.numbering(), self.counter(), self.location()) + { + let numbers = counter.at(vt, location)?.display(vt, &numbering)?; + if !supplement.is_empty() { + supplement += TextElem::packed('\u{a0}'); + } + realized = supplement + numbers + TextElem::packed(": ") + realized; + } + + Ok(realized) + } +} + +cast! { + FigureCaption, + v: Content => v.to::().cloned().unwrap_or_else(|| Self::new(v.clone())), +} + /// The `kind` parameter of a [`FigureElem`]. #[derive(Debug, Clone)] pub enum FigureKind { diff --git a/docs/changelog.md b/docs/changelog.md index 591d2df44..fb3dd2634 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -124,8 +124,8 @@ description: | - Miscellaneous Improvements - Added [`bookmarked`]($heading.bookmarked) argument to heading to control whether a heading becomes part of the PDF outline - - Added [`caption-pos`]($figure.caption-pos) argument to control the position - of a figure's caption + - Added [`caption-pos`]($figure.caption.position) argument to control the + position of a figure's caption - Added [`metadata`]($metadata) function for exposing an arbitrary value to the introspection system - Fixed that a [`state`]($state) was identified by the pair `(key, init)` diff --git a/tests/ref/meta/figure-caption.png b/tests/ref/meta/figure-caption.png new file mode 100644 index 0000000000000000000000000000000000000000..8a1d4a599fc0d982183550caf67a76a3db55ccdd GIT binary patch literal 9192 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 A(EtDd literal 0 HcmV?d00001 diff --git a/tests/typ/meta/figure-caption.typ b/tests/typ/meta/figure-caption.typ new file mode 100644 index 000000000..2a12cc22b --- /dev/null +++ b/tests/typ/meta/figure-caption.typ @@ -0,0 +1,56 @@ +// Test figure captions. + +--- +// Test figure.caption element +#show figure.caption: emph + +#figure( + [Not italicized], + caption: [Italicized], +) + +--- +// Test figure.caption element for specific figure kinds +#show figure.caption.where(kind: table): underline + +#figure( + [Not a table], + caption: [Not underlined], +) + +#figure( + table[A table], + caption: [Underlined], +) + +--- +// Test creating custom figure and custom caption + +#let gap = 0.7em +#show figure.where(kind: "custom"): it => rect(inset: gap, { + align(center, it.body) + v(gap, weak: true) + line(length: 100%) + v(gap, weak: true) + align(center, it.caption) +}) + +#figure( + [A figure], + kind: "custom", + caption: [Hi], + supplement: [A], +) + +#show figure.caption: it => emph[ + #it.body + (#it.supplement + #it.counter.display(it.numbering)) +] + +#figure( + [Another figure], + kind: "custom", + caption: [Hi], + supplement: [B], +) diff --git a/tests/typ/meta/figure.typ b/tests/typ/meta/figure.typ index 48dea0e84..62d163a93 100644 --- a/tests/typ/meta/figure.typ +++ b/tests/typ/meta/figure.typ @@ -41,7 +41,7 @@ We can clearly see that @fig-cylinder and #show figure.where(kind: "theorem"): it => { let name = none if not it.caption == none { - name = [ #emph(it.caption)] + name = [ #emph(it.caption.body)] } else { name = [] } diff --git a/tests/typ/meta/query-figure.typ b/tests/typ/meta/query-figure.typ index b1e59abee..0540d65a6 100644 --- a/tests/typ/meta/query-figure.typ +++ b/tests/typ/meta/query-figure.typ @@ -17,7 +17,7 @@ Figure #numbering(it.numbering, ..counter(figure).at(it.location())): - #it.caption + #it.caption.body #box(width: 1fr, repeat[.]) #counter(page).at(it.location()).first() \ ]