diff --git a/src/exec/context.rs b/src/exec/context.rs index b37b15cb2..d491a251d 100644 --- a/src/exec/context.rs +++ b/src/exec/context.rs @@ -80,6 +80,45 @@ impl<'a> ExecContext<'a> { self.inner.push(node); } + /// Push a normal word space. + pub fn push_space(&mut self) { + let em = self.state.font.font_size(); + self.push(NodeSpacing { + amount: self.state.par.word_spacing.resolve(em), + softness: Softness::Soft, + }); + } + + /// Push text into the context. + /// + /// The text is split into lines at newlines. + pub fn push_text(&mut self, text: &str) { + let mut newline = false; + for line in text.split_terminator(is_newline) { + if newline { + self.apply_linebreak(); + } + + let node = self.make_text_node(line.into()); + self.push(node); + newline = true; + } + } + + /// Execute the body of a function and return the result as a stack node. + pub fn exec_body(&mut self, body: &ValueTemplate, expand: Spec) -> Node { + let dirs = self.state.dirs; + let align = self.state.align; + + self.start_group(ContentGroup); + self.start_par_group(); + body.exec(self); + self.end_par_group(); + let children = self.end_group::().1; + + NodeStack { dirs, align, expand, children }.into() + } + /// Start a page group based on the active page state. /// /// The `softness` is a hint on whether empty pages should be kept in the @@ -130,22 +169,6 @@ impl<'a> ExecContext<'a> { group.softness } - /// Start a content group. - /// - /// This also starts an inner paragraph. - pub fn start_content_group(&mut self) { - self.start_group(ContentGroup); - self.start_par_group(); - } - - /// End a content group and return the resulting nodes. - /// - /// This also ends an inner paragraph. - pub fn end_content_group(&mut self) -> Vec { - self.end_par_group(); - self.end_group::().1 - } - /// Start a paragraph group based on the active text state. pub fn start_par_group(&mut self) { let em = self.state.font.font_size(); @@ -218,29 +241,28 @@ impl<'a> ExecContext<'a> { } } - /// Push a normal word space. - pub fn push_space(&mut self) { - let em = self.state.font.font_size(); - self.push(NodeSpacing { - amount: self.state.par.word_spacing.resolve(em), - softness: Softness::Soft, - }); + /// Set the font to monospace. + pub fn apply_monospace(&mut self) { + let families = self.state.font.families_mut(); + families.list.insert(0, "monospace".to_string()); + families.flatten(); } - /// Push text into the context. - /// - /// The text is split into lines at newlines. - pub fn push_text(&mut self, text: &str) { - let mut newline = false; - for line in text.split_terminator(is_newline) { - if newline { - self.apply_linebreak(); - } + /// Apply a forced line break. + pub fn apply_linebreak(&mut self) { + self.end_par_group(); + self.start_par_group(); + } - let node = self.make_text_node(line.into()); - self.push(node); - newline = true; - } + /// Apply a forced paragraph break. + pub fn apply_parbreak(&mut self) { + self.end_par_group(); + let em = self.state.font.font_size(); + self.push(NodeSpacing { + amount: self.state.par.par_spacing.resolve(em), + softness: Softness::Soft, + }); + self.start_par_group(); } /// Construct a text node from the given string based on the active text @@ -269,30 +291,6 @@ impl<'a> ExecContext<'a> { variant, } } - - /// Set the font to monospace. - pub fn apply_monospace(&mut self) { - let families = self.state.font.families_mut(); - families.list.insert(0, "monospace".to_string()); - families.flatten(); - } - - /// Apply a forced line break. - pub fn apply_linebreak(&mut self) { - self.end_par_group(); - self.start_par_group(); - } - - /// Apply a forced paragraph break. - pub fn apply_parbreak(&mut self) { - self.end_par_group(); - let em = self.state.font.font_size(); - self.push(NodeSpacing { - amount: self.state.par.par_spacing.resolve(em), - softness: Softness::Soft, - }); - self.start_par_group(); - } } /// Defines how an item interacts with surrounding items. diff --git a/src/library/mod.rs b/src/library/mod.rs index f846e6ee5..54d6a14cb 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -7,6 +7,7 @@ mod align; mod base; mod font; mod image; +mod pad; mod page; mod shapes; mod spacing; @@ -15,6 +16,7 @@ pub use self::image::*; pub use align::*; pub use base::*; pub use font::*; +pub use pad::*; pub use page::*; pub use shapes::*; pub use spacing::*; @@ -46,6 +48,7 @@ pub fn new() -> Scope { set!(func: "font", font); set!(func: "h", h); set!(func: "image", image); + set!(func: "pad", pad); set!(func: "page", page); set!(func: "pagebreak", pagebreak); set!(func: "repr", repr); diff --git a/src/library/pad.rs b/src/library/pad.rs new file mode 100644 index 000000000..f9e4386d3 --- /dev/null +++ b/src/library/pad.rs @@ -0,0 +1,38 @@ +use super::*; + +/// `pad`: Pad content at the sides. +/// +/// # Positional arguments +/// - Padding for all sides: `padding`, of type `linear` relative to sides. +/// - Body: of type `template`. +/// +/// # Named arguments +/// - Left padding: `left`, of type `linear` relative to parent width. +/// - Right padding: `right`, of type `linear` relative to parent width. +/// - Top padding: `top`, of type `linear` relative to parent height. +/// - Bottom padding: `bottom`, of type `linear` relative to parent height. +pub fn pad(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { + let all = args.find(ctx); + let left = args.get(ctx, "left"); + let top = args.get(ctx, "top"); + let right = args.get(ctx, "right"); + let bottom = args.get(ctx, "bottom"); + let body = args.require::(ctx, "body").unwrap_or_default(); + + let padding = Sides::new( + left.or(all).unwrap_or_default(), + top.or(all).unwrap_or_default(), + right.or(all).unwrap_or_default(), + bottom.or(all).unwrap_or_default(), + ); + + Value::template("pad", move |ctx| { + let snapshot = ctx.state.clone(); + + let expand = Spec::uniform(Expansion::Fit); + let child = ctx.exec_body(&body, expand); + ctx.push(NodePad { padding, child }); + + ctx.state = snapshot; + }) +} diff --git a/src/library/shapes.rs b/src/library/shapes.rs index f9685fceb..5e638c01a 100644 --- a/src/library/shapes.rs +++ b/src/library/shapes.rs @@ -24,28 +24,18 @@ pub fn box_(ctx: &mut EvalContext, args: &mut ValueArgs) -> Value { let main = args.get(ctx, "main-dir"); let cross = args.get(ctx, "cross-dir"); let color = args.get(ctx, "color"); - let body = args.find::(ctx); + let body = args.find::(ctx).unwrap_or_default(); + + let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit }; + let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some())); Value::template("box", move |ctx| { let snapshot = ctx.state.clone(); ctx.set_dirs(Gen::new(main, cross)); - let dirs = ctx.state.dirs; - let align = ctx.state.align; - ctx.start_content_group(); - if let Some(body) = &body { - body.exec(ctx); - } - let children = ctx.end_content_group(); - - let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit }; - let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some())); - let fixed = NodeFixed { - width, - height, - child: NodeStack { dirs, align, expand, children }.into(), - }; + let child = ctx.exec_body(&body, expand); + let fixed = NodeFixed { width, height, child }; if let Some(color) = color { ctx.push(NodeBackground { diff --git a/tests/ref/library/pad.png b/tests/ref/library/pad.png new file mode 100644 index 000000000..463806891 Binary files /dev/null and b/tests/ref/library/pad.png differ diff --git a/tests/typ/library/pad.typ b/tests/typ/library/pad.typ new file mode 100644 index 000000000..3ee538c22 --- /dev/null +++ b/tests/typ/library/pad.typ @@ -0,0 +1,3 @@ +#box(color: #9feb52)[ + #pad(10pt, box(color: #eb5278, width: 20pt, height: 20pt)) +]