use crate::prelude::*; /// # Padding /// Add spacing around content. /// /// The `pad` function adds spacing around content. The spacing can be specified /// for each side individually, or for all sides at once by specifying a /// positional argument. /// /// ## Example /// ``` /// #set align(center) /// /// #pad(16pt, image("typing.jpg")) /// _Typing speeds can be /// measured in words per minute._ /// ``` /// /// ## Parameters /// - body: Content (positional, required) /// The content to pad at the sides. /// /// - left: Rel (named) /// The padding at the left side. /// /// - right: Rel (named) /// The padding at the right side. /// /// - top: Rel (named) /// The padding at the top side. /// /// - bottom: Rel (named) /// The padding at the bottom side. /// /// - x: Rel (named) /// The horizontal padding. Both `left` and `right` take precedence over this. /// /// - y: Rel (named) /// The vertical padding. Both `top` and `bottom` take precedence over this. /// /// - rest: Rel (named) /// The padding for all sides. All other parameters take precedence over this. /// /// ## Category /// layout #[func] #[capable(Layout)] #[derive(Debug, Hash)] pub struct PadNode { /// The amount of padding. pub padding: Sides>, /// The content whose sides to pad. pub body: Content, } #[node] impl PadNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let all = args.named("rest")?.or(args.find()?); let x = args.named("x")?; let y = args.named("y")?; let left = args.named("left")?.or(x).or(all).unwrap_or_default(); let top = args.named("top")?.or(y).or(all).unwrap_or_default(); let right = args.named("right")?.or(x).or(all).unwrap_or_default(); let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default(); let body = args.expect::("body")?; let padding = Sides::new(left, top, right, bottom); Ok(Self { padding, body }.pack()) } fn field(&self, name: &str) -> Option { match name { "body" => Some(Value::Content(self.body.clone())), _ => None, } } } impl Layout for PadNode { fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult { let mut backlog = vec![]; // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(&mut backlog, |size| shrink(size, padding)); let mut fragment = self.body.layout(vt, styles, pod)?; for frame in &mut fragment { // Apply the padding inversely such that the grown size padded // yields the frame's size. let padded = grow(frame.size(), padding); let padding = padding.relative_to(padded); let offset = Point::new(padding.left, padding.top); // Grow the frame and translate everything in the frame inwards. frame.set_size(padded); frame.translate(offset); } Ok(fragment) } } /// Shrink a size by padding relative to the size itself. fn shrink(size: Size, padding: Sides>) -> Size { size - padding.relative_to(size).sum_by_axis() } /// Grow a size by padding relative to the grown size. /// This is the inverse operation to `shrink()`. /// /// For the horizontal axis the derivation looks as follows. /// (Vertical axis is analogous.) /// /// Let w be the grown target width, /// s be the given width, /// l be the left padding, /// r be the right padding, /// p = l + r. /// /// We want that: w - l.resolve(w) - r.resolve(w) = s /// /// Thus: w - l.resolve(w) - r.resolve(w) = s /// <=> w - p.resolve(w) = s /// <=> w - p.rel * w - p.abs = s /// <=> (1 - p.rel) * w = s + p.abs /// <=> w = (s + p.abs) / (1 - p.rel) fn grow(size: Size, padding: Sides>) -> Size { size.zip(padding.sum_by_axis()) .map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get())) }