From 8469bad7487e111c8e5a0ec542f0232a0ebb4bdc Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Thu, 4 Feb 2021 21:30:18 +0100 Subject: [PATCH 1/3] =?UTF-8?q?Add=20rectangle=20function=20=F0=9F=8E=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/export/pdf.rs | 70 +++++++++++++++++++++++++++--------- src/layout/mod.rs | 69 ++++++++++++++++++++++++++++++++--- src/layout/pad.rs | 10 ++---- src/layout/rect.rs | 71 +++++++++++++++++++++++++++++++++++++ src/library/insert.rs | 37 +++++++++++++++++++ src/library/mod.rs | 1 + tests/library/ref/geom.png | Bin 0 -> 5087 bytes tests/library/typ/geom.typ | 23 ++++++++++++ tests/typeset.rs | 27 +++++++++++++- 9 files changed, 278 insertions(+), 30 deletions(-) create mode 100644 src/layout/rect.rs create mode 100644 tests/library/ref/geom.png create mode 100644 tests/library/typ/geom.typ diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 03c9ae95..7556e370 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -13,9 +13,10 @@ use pdf_writer::{ }; use ttf_parser::{name_id, GlyphId}; +use crate::color::Color; use crate::env::{Env, ImageResource, ResourceId}; use crate::geom::Length; -use crate::layout::{Element, Frame}; +use crate::layout::{Element, Fill, Frame, Shape}; /// Export a collection of frames into a _PDF_ document. /// @@ -57,6 +58,7 @@ impl<'a> PdfExporter<'a> { } images.insert(image.res); } + Element::Geometry(_) => {} } } } @@ -127,6 +129,56 @@ impl<'a> PdfExporter<'a> { fn write_page(&mut self, id: Ref, page: &'a Frame) { let mut content = Content::new(); + for (pos, element) in &page.elements { + match element { + Element::Image(image) => { + let name = format!("Im{}", self.images.map(image.res)); + let size = image.size; + let x = pos.x.to_pt() as f32; + let y = (page.size.height - pos.y - size.height).to_pt() as f32; + let w = size.width.to_pt() as f32; + let h = size.height.to_pt() as f32; + + content.save_state(); + content.matrix(w, 0.0, 0.0, h, x, y); + content.x_object(Name(name.as_bytes())); + content.restore_state(); + } + + Element::Geometry(geometry) => { + content.save_state(); + + match geometry.fill { + Fill::Color(Color::Rgba(c)) => { + content.fill_rgb( + c.r as f32 / 255., + c.g as f32 / 255., + c.b as f32 / 255., + ); + } + Fill::Image(_) => unimplemented!(), + } + + match &geometry.shape { + Shape::Rect(r) => { + content.rect( + pos.x.to_pt() as f32, + (page.size.height - pos.y - r.size.height).to_pt() as f32, + r.size.width.to_pt() as f32, + r.size.height.to_pt() as f32, + false, + true, + ); + } + } + + content.restore_state(); + } + + _ => {} + } + } + // We only write font switching actions when the used face changes. To // do that, we need to remember the active face. let mut face = FaceId::MAX; @@ -153,22 +205,6 @@ impl<'a> PdfExporter<'a> { drop(text); - for (pos, element) in &page.elements { - if let Element::Image(image) = element { - let name = format!("Im{}", self.images.map(image.res)); - let size = image.size; - let x = pos.x.to_pt() as f32; - let y = (page.size.height - pos.y - size.height).to_pt() as f32; - let w = size.width.to_pt() as f32; - let h = size.height.to_pt() as f32; - - content.save_state(); - content.matrix(w, 0.0, 0.0, h, x, y); - content.x_object(Name(name.as_bytes())); - content.restore_state(); - } - } - self.writer.stream(id, &content.finish()); } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 44960de7..1bb4419d 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -4,10 +4,12 @@ mod fixed; mod node; mod pad; mod par; +mod rect; mod spacing; mod stack; mod text; +use crate::color::Color; use crate::env::{Env, ResourceId}; use crate::geom::*; use crate::shaping::Shaped; @@ -16,6 +18,7 @@ pub use fixed::*; pub use node::*; pub use pad::*; pub use par::*; +pub use rect::*; pub use spacing::*; pub use stack::*; pub use text::*; @@ -54,7 +57,7 @@ impl NodePages { pub fn layout(&self, ctx: &mut LayoutContext) -> Vec { let areas = Areas::repeat(self.size); let layouted = self.child.layout(ctx, &areas); - layouted.frames() + layouted.into_frames() } } @@ -157,9 +160,27 @@ pub enum Layouted { } impl Layouted { - /// Return all frames contained in this variant (zero, one or arbitrarily - /// many). - pub fn frames(self) -> Vec { + /// Return a reference to all frames contained in this variant (zero, one or + /// arbitrarily many). + pub fn frames(&self) -> &[Frame] { + match self { + Self::Spacing(_) => &[], + Self::Frame(frame, _) => std::slice::from_ref(frame), + Self::Frames(frames, _) => frames, + } + } + + /// Return a mutable reference to all frames contained in this variant. + pub fn frames_mut(&mut self) -> &mut [Frame] { + match self { + Self::Spacing(_) => &mut [], + Self::Frame(frame, _) => std::slice::from_mut(frame), + Self::Frames(frames, _) => frames, + } + } + + /// Return all frames contained in this varian. + pub fn into_frames(self) -> Vec { match self { Self::Spacing(_) => vec![], Self::Frame(frame, _) => vec![frame], @@ -204,6 +225,46 @@ pub enum Element { Text(Shaped), /// An image. Image(Image), + /// Some shape that could hold another frame. + Geometry(Geometry), +} + +/// The kind of graphic fill to be applied to a [`Shape`]. +#[derive(Debug, Clone, PartialEq)] +pub enum Fill { + /// The fill is a color. + Color(Color), + /// The fill is an image. + Image(Image), + // Gradient(Gradient), +} + +/// A shape with some kind of fill. +#[derive(Debug, Clone, PartialEq)] +pub struct Geometry { + /// The shape to draw. + pub shape: Shape, + /// How the shape looks on the inside. + /// + /// **TODO:** This could be made into a Vec or something such that + /// the user can compose multiple fills with alpha values less + /// than one to achieve cool effects. + pub fill: Fill, +} + +/// Some shape. +#[derive(Debug, Clone, PartialEq)] +pub enum Shape { + /// A rectangle. + Rect(Rect), + // Ellipse(Ellipse), +} + +/// An rectangle. +#[derive(Debug, Clone, PartialEq)] +pub struct Rect { + /// The dimensions of the rectangle. + pub size: Size, } /// An image element. diff --git a/src/layout/pad.rs b/src/layout/pad.rs index f8a623e3..425fa41b 100644 --- a/src/layout/pad.rs +++ b/src/layout/pad.rs @@ -15,14 +15,8 @@ impl Layout for NodePad { let areas = shrink(areas, self.padding); let mut layouted = self.child.layout(ctx, &areas); - match &mut layouted { - Layouted::Spacing(_) => {} - Layouted::Frame(frame, _) => pad(frame, self.padding), - Layouted::Frames(frames, _) => { - for frame in frames { - pad(frame, self.padding); - } - } + for frame in layouted.frames_mut() { + pad(frame, self.padding); } layouted diff --git a/src/layout/rect.rs b/src/layout/rect.rs new file mode 100644 index 00000000..ab8e3b07 --- /dev/null +++ b/src/layout/rect.rs @@ -0,0 +1,71 @@ +use std::cmp::Ordering; + +use super::*; +use crate::{color::RgbaColor, geom::Linear}; + +/// A node that represents a rectangular box. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeRect { + /// The fixed width, if any. + pub width: Option, + /// The fixed height, if any. + pub height: Option, + /// The background color. + pub color: Option, + /// The child node whose sides to pad. + pub child: Node, +} + +impl Layout for NodeRect { + fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { + let Areas { current, full, .. } = areas; + + let height_opt = self.height.map(|h| h.resolve(full.height)); + let mut size = Size::new( + self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width), + height_opt.unwrap_or(current.height), + ); + + let areas = Areas::once(size); + let mut layouted = self.child.layout(ctx, &areas); + + // If the children have some height, apply that, + // otherwise fall back to zero or the height property. + if let Some(max) = layouted + .frames() + .iter() + .map(|f| f.size.height) + .max_by(|x, y| x.partial_cmp(y).unwrap_or(Ordering::Equal)) + { + size.height = max; + } else { + size.height = height_opt.unwrap_or(Length::ZERO) + } + + if let Some(first) = layouted.frames_mut().first_mut() { + first.elements.insert( + 0, + ( + Point::ZERO, + Element::Geometry(Geometry { + shape: Shape::Rect(Rect { size }), + fill: Fill::Color(self.color.unwrap_or(Color::Rgba(RgbaColor { + r: 255, + g: 255, + b: 255, + a: 0, + }))), + }), + ), + ) + } + + layouted + } +} + +impl From for NodeAny { + fn from(pad: NodeRect) -> Self { + Self::new(pad) + } +} diff --git a/src/library/insert.rs b/src/library/insert.rs index 58e8a11c..169fad97 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -4,6 +4,43 @@ use crate::env::{ImageResource, ResourceId}; use crate::layout::*; use crate::prelude::*; +/// `rect`: Layout content into a rectangle that also might have a fill. +/// +/// # Named arguments +/// - Width of the box: `width`, of type `linear` relative to parent width. +/// - Height of the box: `height`, of type `linear` relative to parent height. +pub fn rect(ctx: &mut EvalContext, args: &mut Args) -> Value { + let snapshot = ctx.state.clone(); + + let width = args.get(ctx, "width"); + let height = args.get(ctx, "height"); + let color = args.get(ctx, "color"); + + let dirs = ctx.state.dirs; + let align = ctx.state.align; + + ctx.start_content_group(); + + if let Some(body) = args.find::(ctx) { + body.eval(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())); + + ctx.push(NodeRect { + width, + height, + color, + child: NodeStack { dirs, align, expand, children }.into(), + }); + + ctx.state = snapshot; + Value::None +} + /// `image`: Insert an image. /// /// Supports PNG and JPEG files. diff --git a/src/library/mod.rs b/src/library/mod.rs index 48da093b..7e20f5fb 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -38,6 +38,7 @@ pub fn new() -> Scope { set!(func: "image", image); set!(func: "page", page); set!(func: "pagebreak", pagebreak); + set!(func: "rect", rect); set!(func: "rgb", rgb); set!(func: "type", type_); set!(func: "v", v); diff --git a/tests/library/ref/geom.png b/tests/library/ref/geom.png new file mode 100644 index 0000000000000000000000000000000000000000..37fd7d273f3da44511ab85fff28f29bc41a3245a GIT binary patch literal 5087 zcmeHLdsLHG5(g<_p&GZk25mxUeOF#Scm@nmEg*rgT1pkago+X|je!^?1VT_&X^ViC zR%{4_JtFKHNy=7azWbY* zJ9F=R_sq=wK8uMCTk5#R(ZfLt!u|1+seh8K&)>ZU_H98b7NF-8z zz@9BJ2f~s7==QGY&jRQ1|&ZpQ$DD3|k4*h-|umGZ-0a=}vX`}k&8U7F9a{?I= z^o|0XAu?YF{%8Ny(^F%@7=emD(KBIDYmG3XoM0YAq_MJ z?8!7>j8+z}l?a|3J1pGpnPM?hl?R|>0sTIJ1Fj%8#-*BO8WJ2rL_)^|eO4Yjf8+ck z9&YyIPEKK531-gC>U?39LgSFLx-P9tMB7H%106eH)#W(7lNC6xl{Yt{vdn{jHp*;| zKalfIuhT9%R^fD1Kor=hRx0dwL_lNK0>MQTTkjVH7B4N0U~cRu&D>Yzy@3;oOlKe| zQ{O{uS6Vm0Dd=NRC-27(429f~-kXb-AIH1l4o{I;!a+OMkUCvNQH3MZyDl*y^7-!lF6rEb{6X8gtheKL_zMGy(fmCBTG7)b$G{UHyXzv$p>RF?d{VKV@B4DtDOUvD8Es}apvum#dQ)0(-5+s+wluyUM?exqw13-`ia z)m2_>OAg2g45I(&gYYV7D@|s)@SAPO3aiqg8x!#j zBHH3S(5esp_`?~OU5dPU(Kl{Ylx7mx0Z3ppUq@d;rL2aljZ zzs5SUPm?#`d%o{2_1xqM@TB(?rQH`Zqi!)p8DIY^C)OfWPgJ*>DAf;k-RtwBEP=bQUGtI&=#y&xkQ3jHmjyxU_ zUD6nPEDaQyCvncNiXd`Du|q}ERG))KA&f2B1Mt;l9nwr+nZPISA=r0ab5%E?1xW7F z)(4eVYq3m{3g;OuD{IP5G>;2S`Z}&usc1-}cX{gzi)wnOR-K^)8QxGoco*0RQqoiD z)jF+fX?xo+!yS;HXLYsBBYm$DMAH=azx1kG-coq;L_nl+JSp~*rjEpDWfGl`+9}E9 zIP?nquq?KJ^AZy!3Q@+bBQv&x0u+K-S44P)H57H7gv**FkU~y>Jl0{6nO5XU6GcI| zPiy?094kO?BAi{A+SG?my`)Km@t~dN=q^O`G7aWKgo}JF6w(z3^2F197m*MNKd?-L z0qf~IC3E%kT3?9jdIE7Vu3s2HNb#5Ce7m=#7s}C0Qk#f9We5^U)jHeoRZB~C=k`99 z%SWZba4Dxnp*G=;9WRB={OL69r!2hk`LpneUhAQvyf#J)|3%_9)@o_6D{HINT^ZMkaI?dVtQr+K_3%ejmQjZpO^-Vr zr#}LkbmB~7r?Ys!>DcUenwmqB7*ysPi+DYem4uVuQ4Pj%9EXxGH^AZaojacjba5QI z5H-i&qckXyvq{u=tEG(nmGhR%#oEGxrZtFk-08THj0k0%+Tss{v8`q58Xc!);(@TR z`7OXL&ZMY9rN-`xrSuNo;tx3#Oi<@k%Gtf#=;V9fP0dkH!E>xkW2SN5U(seM_ zD6Uxa?|$StTaN!bHR0hG=@LMqwFoznP>+lbiuHgi@Pvdz!;2}WDG>|g?FFX&6a2H` z&qUT43VX%b1%2p(>aT}m#}xKsl@~n9BLvxNw2PP(PJh4n5&*NelEAEFET#s%BnK~X z?JwChb^~BTa{OJP|CT7IOr8!aBN#m_cJhX-qPfpBU#!=D;j!?93MwL>JWfv;Ke3T^FO%Qyb9v0Ain6O_|@6@oo5HU mdDW>r^tWm`bE{ Canvas { Element::Image(image) => { draw_image(&mut canvas, pos, env, image); } + Element::Geometry(geom) => { + draw_geometry(&mut canvas, pos, env, geom); + } } } @@ -444,6 +447,28 @@ fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) { } } +fn draw_geometry(canvas: &mut Canvas, pos: Point, _: &Env, element: &Geometry) { + let x = pos.x.to_pt() as f32; + let y = pos.y.to_pt() as f32; + + let (w, h) = match &element.shape { + Shape::Rect(s) => (s.size.width.to_pt() as f32, s.size.height.to_pt() as f32), + }; + + let mut paint = Paint::default(); + + match &element.fill { + Fill::Color(c) => match c { + typst::color::Color::Rgba(c) => paint.set_color_rgba8(c.r, c.g, c.b, c.a), + }, + Fill::Image(_) => todo!(), + }; + + if let Some(rect) = Rect::from_xywh(x, y, w, h) { + canvas.fill_rect(rect, &paint); + } +} + fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) { let img = &env.resources.loaded::(element.res); From 80e076814dde330fb2136172580f11e939bc6601 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sat, 6 Feb 2021 12:30:44 +0100 Subject: [PATCH 2/3] =?UTF-8?q?Merge=20`rect`=20and=20`box`=20=F0=9F=A6=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/export/pdf.rs | 18 +++--- src/layout/background.rs | 37 ++++++++++++ src/layout/mod.rs | 23 +++----- src/layout/rect.rs | 71 ------------------------ src/library/insert.rs | 37 ------------ src/library/layout.rs | 20 +++++-- src/library/mod.rs | 1 - tests/library/ref/{geom.png => box.png} | Bin tests/library/typ/box.typ | 23 ++++++++ tests/library/typ/geom.typ | 23 -------- tests/typeset.rs | 15 ++--- 11 files changed, 100 insertions(+), 168 deletions(-) create mode 100644 src/layout/background.rs delete mode 100644 src/layout/rect.rs rename tests/library/ref/{geom.png => box.png} (100%) create mode 100644 tests/library/typ/box.typ delete mode 100644 tests/library/typ/geom.typ diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 7556e370..e5124c21 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -156,19 +156,19 @@ impl<'a> PdfExporter<'a> { c.b as f32 / 255., ); } - Fill::Image(_) => unimplemented!(), + Fill::Image(_) => todo!(), } + let x = pos.x.to_pt() as f32; + match &geometry.shape { Shape::Rect(r) => { - content.rect( - pos.x.to_pt() as f32, - (page.size.height - pos.y - r.size.height).to_pt() as f32, - r.size.width.to_pt() as f32, - r.size.height.to_pt() as f32, - false, - true, - ); + let w = r.width.to_pt() as f32; + let h = r.height.to_pt() as f32; + let y = (page.size.height - pos.y - r.height).to_pt() as f32; + if w > 0.0 && h > 0.0 { + content.rect(x, y, w, h, false, true); + } } } diff --git a/src/layout/background.rs b/src/layout/background.rs new file mode 100644 index 00000000..94d5bf45 --- /dev/null +++ b/src/layout/background.rs @@ -0,0 +1,37 @@ +use super::*; + +/// A node that represents a rectangular box. +#[derive(Debug, Clone, PartialEq)] +pub struct NodeBackground { + /// The background fill. + pub fill: Fill, + /// The child node whose sides to pad. + pub child: NodeFixed, +} + +impl Layout for NodeBackground { + fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { + let mut layouted = self.child.layout(ctx, areas); + + if let Some(first) = layouted.frames_mut().first_mut() { + first.elements.insert( + 0, + ( + Point::ZERO, + Element::Geometry(Geometry { + shape: Shape::Rect(first.size), + fill: self.fill.clone(), + }), + ), + ) + } + + layouted + } +} + +impl From for NodeAny { + fn from(background: NodeBackground) -> Self { + Self::new(background) + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 1bb4419d..0c7a6a68 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -4,7 +4,7 @@ mod fixed; mod node; mod pad; mod par; -mod rect; +mod background; mod spacing; mod stack; mod text; @@ -18,7 +18,7 @@ pub use fixed::*; pub use node::*; pub use pad::*; pub use par::*; -pub use rect::*; +pub use background::*; pub use spacing::*; pub use stack::*; pub use text::*; @@ -236,7 +236,6 @@ pub enum Fill { Color(Color), /// The fill is an image. Image(Image), - // Gradient(Gradient), } /// A shape with some kind of fill. @@ -245,10 +244,10 @@ pub struct Geometry { /// The shape to draw. pub shape: Shape, /// How the shape looks on the inside. - /// - /// **TODO:** This could be made into a Vec or something such that - /// the user can compose multiple fills with alpha values less - /// than one to achieve cool effects. + // + // TODO: This could be made into a Vec or something such that + // the user can compose multiple fills with alpha values less + // than one to achieve cool effects. pub fill: Fill, } @@ -256,15 +255,7 @@ pub struct Geometry { #[derive(Debug, Clone, PartialEq)] pub enum Shape { /// A rectangle. - Rect(Rect), - // Ellipse(Ellipse), -} - -/// An rectangle. -#[derive(Debug, Clone, PartialEq)] -pub struct Rect { - /// The dimensions of the rectangle. - pub size: Size, + Rect(Size), } /// An image element. diff --git a/src/layout/rect.rs b/src/layout/rect.rs deleted file mode 100644 index ab8e3b07..00000000 --- a/src/layout/rect.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::cmp::Ordering; - -use super::*; -use crate::{color::RgbaColor, geom::Linear}; - -/// A node that represents a rectangular box. -#[derive(Debug, Clone, PartialEq)] -pub struct NodeRect { - /// The fixed width, if any. - pub width: Option, - /// The fixed height, if any. - pub height: Option, - /// The background color. - pub color: Option, - /// The child node whose sides to pad. - pub child: Node, -} - -impl Layout for NodeRect { - fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted { - let Areas { current, full, .. } = areas; - - let height_opt = self.height.map(|h| h.resolve(full.height)); - let mut size = Size::new( - self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width), - height_opt.unwrap_or(current.height), - ); - - let areas = Areas::once(size); - let mut layouted = self.child.layout(ctx, &areas); - - // If the children have some height, apply that, - // otherwise fall back to zero or the height property. - if let Some(max) = layouted - .frames() - .iter() - .map(|f| f.size.height) - .max_by(|x, y| x.partial_cmp(y).unwrap_or(Ordering::Equal)) - { - size.height = max; - } else { - size.height = height_opt.unwrap_or(Length::ZERO) - } - - if let Some(first) = layouted.frames_mut().first_mut() { - first.elements.insert( - 0, - ( - Point::ZERO, - Element::Geometry(Geometry { - shape: Shape::Rect(Rect { size }), - fill: Fill::Color(self.color.unwrap_or(Color::Rgba(RgbaColor { - r: 255, - g: 255, - b: 255, - a: 0, - }))), - }), - ), - ) - } - - layouted - } -} - -impl From for NodeAny { - fn from(pad: NodeRect) -> Self { - Self::new(pad) - } -} diff --git a/src/library/insert.rs b/src/library/insert.rs index 169fad97..58e8a11c 100644 --- a/src/library/insert.rs +++ b/src/library/insert.rs @@ -4,43 +4,6 @@ use crate::env::{ImageResource, ResourceId}; use crate::layout::*; use crate::prelude::*; -/// `rect`: Layout content into a rectangle that also might have a fill. -/// -/// # Named arguments -/// - Width of the box: `width`, of type `linear` relative to parent width. -/// - Height of the box: `height`, of type `linear` relative to parent height. -pub fn rect(ctx: &mut EvalContext, args: &mut Args) -> Value { - let snapshot = ctx.state.clone(); - - let width = args.get(ctx, "width"); - let height = args.get(ctx, "height"); - let color = args.get(ctx, "color"); - - let dirs = ctx.state.dirs; - let align = ctx.state.align; - - ctx.start_content_group(); - - if let Some(body) = args.find::(ctx) { - body.eval(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())); - - ctx.push(NodeRect { - width, - height, - color, - child: NodeStack { dirs, align, expand, children }.into(), - }); - - ctx.state = snapshot; - Value::None -} - /// `image`: Insert an image. /// /// Supports PNG and JPEG files. diff --git a/src/library/layout.rs b/src/library/layout.rs index 0e04c507..bbcd7898 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Display, Formatter}; -use crate::eval::Softness; -use crate::layout::{Expansion, NodeFixed, NodeSpacing, NodeStack}; +use crate::{eval::Softness, layout::NodeBackground}; +use crate::layout::{Expansion, Fill, NodeFixed, NodeSpacing, NodeStack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; @@ -175,6 +175,7 @@ impl Display for Alignment { /// # Named arguments /// - Width of the box: `width`, of type `linear` relative to parent width. /// - Height of the box: `height`, of type `linear` relative to parent height. +/// - Background color of the box: `color`, of type `color`. pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { let snapshot = ctx.state.clone(); @@ -182,6 +183,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { let height = args.get(ctx, "height"); let main = args.get(ctx, "main-dir"); let cross = args.get(ctx, "cross-dir"); + let color = args.get(ctx, "color"); ctx.set_dirs(Gen::new(main, cross)); @@ -199,11 +201,21 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { 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())); - ctx.push(NodeFixed { + let fixed_node = NodeFixed { width, height, child: NodeStack { dirs, align, expand, children }.into(), - }); + }; + + if let Some(color) = color { + ctx.push(NodeBackground { + fill: Fill::Color(color), + child: fixed_node, + }) + } else { + ctx.push(fixed_node); + } + ctx.state = snapshot; Value::None diff --git a/src/library/mod.rs b/src/library/mod.rs index 7e20f5fb..48da093b 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -38,7 +38,6 @@ pub fn new() -> Scope { set!(func: "image", image); set!(func: "page", page); set!(func: "pagebreak", pagebreak); - set!(func: "rect", rect); set!(func: "rgb", rgb); set!(func: "type", type_); set!(func: "v", v); diff --git a/tests/library/ref/geom.png b/tests/library/ref/box.png similarity index 100% rename from tests/library/ref/geom.png rename to tests/library/ref/box.png diff --git a/tests/library/typ/box.typ b/tests/library/typ/box.typ new file mode 100644 index 00000000..03e5da54 --- /dev/null +++ b/tests/library/typ/box.typ @@ -0,0 +1,23 @@ +#[page "a5", flip: true] + +// Rectangle with width, should have paragraph height +#[box width: 2cm, color: #9650D6][aa] + +Sometimes there is no box + +// Rectangle with height, should span line +#[box height: 2cm, width: 100%, color: #734CED][bb] + +// Empty rectangle with width and height +#[box width: 6cm, height: 12pt, color: #CB4CED] + +// This empty rectangle should not be displayed +#[box width: 2in, color: #ff0000] + +// This one should be +#[box height: 15mm, width: 100%, color: #494DE3] + +// These are in a row! +#[box width: 2in, height: 10pt, color: #D6CD67] +#[box width: 2in, height: 10pt, color: #EDD466] +#[box width: 2in, height: 10pt, color: #E3BE62] diff --git a/tests/library/typ/geom.typ b/tests/library/typ/geom.typ deleted file mode 100644 index 26ba7ca3..00000000 --- a/tests/library/typ/geom.typ +++ /dev/null @@ -1,23 +0,0 @@ -#[page "a5", flip: true] - -// Rectangle with width, should have paragraph height -#[rect width: 2cm, color: #9650D6][aa] - -Sometimes there is no box - -// Rectangle with height, should span line -#[rect height: 2cm, color: #734CED][bb] - -// Empty rectangle with width and height -#[rect width: 6cm, height: 12pt, color: #CB4CED] - -// This empty rectangle should not be displayed -#[rect width: 2in, color: #ff0000] - -// This one should be -#[rect height: 15mm, color: #494DE3] - -// These are in a row! -#[rect width: 2in, height: 10pt, color: #D6CD67] -#[rect width: 2in, height: 10pt, color: #EDD466] -#[rect width: 2in, height: 10pt, color: #E3BE62] diff --git a/tests/typeset.rs b/tests/typeset.rs index 432941ce..b1f4ede2 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -451,10 +451,6 @@ fn draw_geometry(canvas: &mut Canvas, pos: Point, _: &Env, element: &Geometry) { let x = pos.x.to_pt() as f32; let y = pos.y.to_pt() as f32; - let (w, h) = match &element.shape { - Shape::Rect(s) => (s.size.width.to_pt() as f32, s.size.height.to_pt() as f32), - }; - let mut paint = Paint::default(); match &element.fill { @@ -464,9 +460,14 @@ fn draw_geometry(canvas: &mut Canvas, pos: Point, _: &Env, element: &Geometry) { Fill::Image(_) => todo!(), }; - if let Some(rect) = Rect::from_xywh(x, y, w, h) { - canvas.fill_rect(rect, &paint); - } + match &element.shape { + Shape::Rect(s) => { + let (w, h) = (s.width.to_pt() as f32, s.height.to_pt() as f32); + canvas.fill_rect(Rect::from_xywh(x, y, w, h).unwrap(), &paint); + }, + }; + + } fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) { From a6cae89b47246a235ed7b1093747c6f3bcb64da4 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Sat, 6 Feb 2021 12:54:44 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Generalize=20child=20of=20NodeBackground=20?= =?UTF-8?q?=F0=9F=8D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/background.rs | 4 ++-- src/layout/mod.rs | 4 ++-- src/library/layout.rs | 4 ++-- tests/typeset.rs | 4 +--- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/layout/background.rs b/src/layout/background.rs index 94d5bf45..07248e02 100644 --- a/src/layout/background.rs +++ b/src/layout/background.rs @@ -5,8 +5,8 @@ use super::*; pub struct NodeBackground { /// The background fill. pub fill: Fill, - /// The child node whose sides to pad. - pub child: NodeFixed, + /// The child node to be filled in. + pub child: Node, } impl Layout for NodeBackground { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 0c7a6a68..30295841 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,10 +1,10 @@ //! Layouting. +mod background; mod fixed; mod node; mod pad; mod par; -mod background; mod spacing; mod stack; mod text; @@ -14,11 +14,11 @@ use crate::env::{Env, ResourceId}; use crate::geom::*; use crate::shaping::Shaped; +pub use background::*; pub use fixed::*; pub use node::*; pub use pad::*; pub use par::*; -pub use background::*; pub use spacing::*; pub use stack::*; pub use text::*; diff --git a/src/library/layout.rs b/src/library/layout.rs index bbcd7898..2812a48f 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,9 +1,9 @@ use std::fmt::{self, Display, Formatter}; -use crate::{eval::Softness, layout::NodeBackground}; use crate::layout::{Expansion, Fill, NodeFixed, NodeSpacing, NodeStack}; use crate::paper::{Paper, PaperClass}; use crate::prelude::*; +use crate::{eval::Softness, layout::NodeBackground}; /// `align`: Align content along the layouting axes. /// @@ -210,7 +210,7 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value { if let Some(color) = color { ctx.push(NodeBackground { fill: Fill::Color(color), - child: fixed_node, + child: Node::Any(fixed_node.into()), }) } else { ctx.push(fixed_node); diff --git a/tests/typeset.rs b/tests/typeset.rs index b1f4ede2..d431abfe 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -464,10 +464,8 @@ fn draw_geometry(canvas: &mut Canvas, pos: Point, _: &Env, element: &Geometry) { Shape::Rect(s) => { let (w, h) = (s.width.to_pt() as f32, s.height.to_pt() as f32); canvas.fill_rect(Rect::from_xywh(x, y, w, h).unwrap(), &paint); - }, + } }; - - } fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, element: &Image) {