summaryrefslogtreecommitdiff
path: root/library/src/layout/container.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-02-13 12:04:26 +0100
committerLaurenz <laurmaedje@gmail.com>2023-02-13 12:18:34 +0100
commit72b60dfde751b4a2ab279aa1fcfa559b4a75eb51 (patch)
tree013065649ef0972bd342ad5c5821842a9c48c740 /library/src/layout/container.rs
parentdb49b628f73d3c328aacadbb9126616e7cccfc49 (diff)
Fill and stroke properties for containers
Diffstat (limited to 'library/src/layout/container.rs')
-rw-r--r--library/src/layout/container.rs190
1 files changed, 165 insertions, 25 deletions
diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs
index 7814c083..43823339 100644
--- a/library/src/layout/container.rs
+++ b/library/src/layout/container.rs
@@ -60,18 +60,55 @@ pub struct BoxNode {
pub width: Sizing,
/// The box's height.
pub height: Smart<Rel<Length>>,
- /// The box's baseline shift.
- pub baseline: Rel<Length>,
}
#[node]
impl BoxNode {
+ /// The box's baseline shift.
+ #[property(resolve)]
+ pub const BASELINE: Rel<Length> = Rel::zero();
+
+ /// The box's background color. See the
+ /// [rectangle's documentation]($func/rect.fill) for more details.
+ pub const FILL: Option<Paint> = None;
+
+ /// The box's border color. See the
+ /// [rectangle's documentation]($func/rect.stroke) for more details.
+ #[property(resolve, fold)]
+ pub const STROKE: Sides<Option<Option<PartialStroke>>> = Sides::splat(None);
+
+ /// How much to round the box's corners. See the [rectangle's
+ /// documentation]($func/rect.radius) for more details.
+ #[property(resolve, fold)]
+ pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
+
+ /// How much to pad the box's content. See the [rectangle's
+ /// documentation]($func/rect.inset) for more details.
+ #[property(resolve, fold)]
+ pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
+
+ /// How much to expand the box's size without affecting the layout.
+ ///
+ /// This is useful to prevent padding from affecting line layout. For a
+ /// generalized version of the example below, see the documentation for the
+ /// [raw text's block parameter]($func/raw.block).
+ ///
+ /// ```example
+ /// An inline
+ /// #box(
+ /// fill: luma(235),
+ /// inset: (x: 3pt, y: 0pt),
+ /// outset: (y: 3pt),
+ /// radius: 2pt,
+ /// )[rectangle].
+ #[property(resolve, fold)]
+ pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
+
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
- let body = args.eat::<Content>()?.unwrap_or_default();
+ let body = args.eat()?.unwrap_or_default();
let width = args.named("width")?.unwrap_or_default();
let height = args.named("height")?.unwrap_or_default();
- let baseline = args.named("baseline")?.unwrap_or_default();
- Ok(Self { body, width, height, baseline }.pack())
+ Ok(Self { body, width, height }.pack())
}
}
@@ -96,42 +133,86 @@ impl Layout for BoxNode {
.map(|(s, b)| s.map(|v| v.relative_to(b)))
.unwrap_or(regions.size);
+ // Apply inset.
+ let mut child = self.body.clone();
+ let inset = styles.get(Self::INSET);
+ if inset.iter().any(|v| !v.is_zero()) {
+ child = child.clone().padded(inset.map(|side| side.map(Length::from)));
+ }
+
// Select the appropriate base and expansion for the child depending
// on whether it is automatically or relatively sized.
let is_auto = sizing.as_ref().map(Smart::is_auto);
let expand = regions.expand | !is_auto;
let pod = Regions::one(size, expand);
- let mut frame = self.body.layout(vt, styles, pod)?.into_frame();
+ let mut frame = child.layout(vt, styles, pod)?.into_frame();
// Apply baseline shift.
- let shift = self.baseline.resolve(styles).relative_to(frame.height());
+ let shift = styles.get(Self::BASELINE).relative_to(frame.height());
if !shift.is_zero() {
frame.set_baseline(frame.baseline() - shift);
}
+ // Prepare fill and stroke.
+ let fill = styles.get(Self::FILL);
+ let stroke = styles
+ .get(Self::STROKE)
+ .map(|s| s.map(PartialStroke::unwrap_or_default));
+
+ // Add fill and/or stroke.
+ if fill.is_some() || stroke.iter().any(Option::is_some) {
+ let outset = styles.get(Self::OUTSET);
+ let radius = styles.get(Self::RADIUS);
+ frame.fill_and_stroke(fill, stroke, outset, radius);
+ }
+
+ // Apply metadata.
+ frame.meta(styles);
+
Ok(Fragment::frame(frame))
}
}
/// # Block
-/// A block-level container that places content into a separate flow.
+/// A block-level container.
///
-/// This can be used to force elements that would otherwise be inline to become
-/// block-level. This is especially useful when writing show rules.
+/// Such a container can be used to separate content, size it and give it a
+/// background or border.
///
-/// ## Example
+/// ## Examples
+/// With a block, you can give a background to content while still allowing it
+/// to break across multiple pages. The documentation examples can only have a
+/// single page, but the example below demonstrates how this would work.
/// ```example
-/// #[
-/// #show heading: it => it.title
-/// = No block
-/// Some text
-/// ]
-///
-/// #[
-/// #show heading: it => block(it.title)
-/// = Block
-/// Some more text
-/// ]
+/// #block(
+/// fill: luma(230),
+/// inset: 8pt,
+/// radius: 4pt,
+/// lorem(30),
+/// )
+/// ```
+///
+/// Blocks are also useful to force elements that would otherwise be inline to
+/// become block-level, especially when writing show rules.
+/// ```example
+/// #show heading: it => it.title
+/// = Blockless
+/// More text.
+///
+/// #show heading: it => block(it.title)
+/// = Blocky
+/// More text.
+/// ```
+///
+/// Last but not least, set rules for the block function can be used to
+/// configure the spacing around arbitrary block-level elements.
+/// ```example
+/// #set align(center)
+/// #show math.formula: set block(above: 8pt, below: 16pt)
+///
+/// This sum of $x$ and $y$:
+/// $ x + y = z $
+/// A second paragraph.
/// ```
///
/// ## Parameters
@@ -158,16 +239,44 @@ impl Layout for BoxNode {
#[func]
#[capable(Layout)]
#[derive(Debug, Hash)]
-pub struct BlockNode(pub Content);
+pub struct BlockNode {
+ pub body: Content,
+}
#[node]
impl BlockNode {
+ /// The block's background color. See the
+ /// [rectangle's documentation]($func/rect.fill) for more details.
+ pub const FILL: Option<Paint> = None;
+
+ /// The block's border color. See the
+ /// [rectangle's documentation]($func/rect.stroke) for more details.
+ #[property(resolve, fold)]
+ pub const STROKE: Sides<Option<Option<PartialStroke>>> = Sides::splat(None);
+
+ /// How much to round the block's corners. See the [rectangle's
+ /// documentation]($func/rect.radius) for more details.
+ #[property(resolve, fold)]
+ pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
+
+ /// How much to pad the block's content. See the [rectangle's
+ /// documentation]($func/rect.inset) for more details.
+ #[property(resolve, fold)]
+ pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
+
+ /// How much to expand the block's size without affecting the layout. See
+ /// the [rectangle's documentation]($func/rect.outset) for more details.
+ #[property(resolve, fold)]
+ pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
+
/// The spacing between the previous and this block.
#[property(skip)]
pub const ABOVE: VNode = VNode::block_spacing(Em::new(1.2).into());
+
/// The spacing between this and the following block.
#[property(skip)]
pub const BELOW: VNode = VNode::block_spacing(Em::new(1.2).into());
+
/// Whether this block must stick to the following one.
///
/// Use this to prevent page breaks between e.g. a heading and its body.
@@ -175,7 +284,8 @@ impl BlockNode {
pub const STICKY: bool = false;
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
- Ok(Self(args.eat()?.unwrap_or_default()).pack())
+ let body = args.eat()?.unwrap_or_default();
+ Ok(Self { body }.pack())
}
fn set(...) {
@@ -198,7 +308,37 @@ impl Layout for BlockNode {
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- self.0.layout(vt, styles, regions)
+ // Apply inset.
+ let mut child = self.body.clone();
+ let inset = styles.get(Self::INSET);
+ if inset.iter().any(|v| !v.is_zero()) {
+ child = child.clone().padded(inset.map(|side| side.map(Length::from)));
+ }
+
+ // Layout the child.
+ let mut frames = child.layout(vt, styles, regions)?.into_frames();
+
+ // Prepare fill and stroke.
+ let fill = styles.get(Self::FILL);
+ let stroke = styles
+ .get(Self::STROKE)
+ .map(|s| s.map(PartialStroke::unwrap_or_default));
+
+ // Add fill and/or stroke.
+ if fill.is_some() || stroke.iter().any(Option::is_some) {
+ let outset = styles.get(Self::OUTSET);
+ let radius = styles.get(Self::RADIUS);
+ for frame in &mut frames {
+ frame.fill_and_stroke(fill, stroke, outset, radius);
+ }
+ }
+
+ // Apply metadata.
+ for frame in &mut frames {
+ frame.meta(styles);
+ }
+
+ Ok(Fragment::frames(frames))
}
}