diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 4495bceb3..cc84dbc3c 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -611,19 +611,24 @@ impl<'a, 'b> PageExporter<'a, 'b> { } fn export(mut self, frame: &Frame) -> Page { + let size = frame.size(); + // Make the coordinate system start at the top-left. - self.bottom = frame.size.y.to_f32(); + self.bottom = size.y.to_f32(); self.transform(Transform { sx: Ratio::one(), ky: Ratio::zero(), kx: Ratio::zero(), sy: Ratio::new(-1.0), tx: Length::zero(), - ty: frame.size.y, + ty: size.y, }); + + // Encode the page into the content stream. self.write_frame(frame); + Page { - size: frame.size, + size, content: self.content, id: self.page_ref, links: self.links, @@ -648,7 +653,7 @@ impl<'a, 'b> PageExporter<'a, 'b> { } } - for &(pos, ref element) in &frame.elements { + for &(pos, ref element) in frame.elements() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); match *element { @@ -669,8 +674,9 @@ impl<'a, 'b> PageExporter<'a, 'b> { self.transform(translation.pre_concat(group.transform)); if group.clips { - let w = group.frame.size.x.to_f32(); - let h = group.frame.size.y.to_f32(); + let size = group.frame.size(); + let w = size.x.to_f32(); + let h = size.y.to_f32(); self.content.move_to(0.0, 0.0); self.content.line_to(w, 0.0); self.content.line_to(w, h); diff --git a/src/export/render.rs b/src/export/render.rs index 9f088433a..1387015be 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -23,8 +23,9 @@ use crate::Context; /// compilation so that fonts and images can be rendered and rendering artifacts /// can be cached. pub fn render(ctx: &Context, frame: &Frame, pixel_per_pt: f32) -> sk::Pixmap { - let pxw = (pixel_per_pt * frame.size.x.to_f32()).round().max(1.0) as u32; - let pxh = (pixel_per_pt * frame.size.y.to_f32()).round().max(1.0) as u32; + let size = frame.size(); + let pxw = (pixel_per_pt * size.x.to_f32()).round().max(1.0) as u32; + let pxh = (pixel_per_pt * size.y.to_f32()).round().max(1.0) as u32; let mut canvas = sk::Pixmap::new(pxw, pxh).unwrap(); canvas.fill(sk::Color::WHITE); @@ -43,7 +44,7 @@ fn render_frame( ctx: &Context, frame: &Frame, ) { - for (pos, element) in &frame.elements { + for (pos, element) in frame.elements() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); let ts = ts.pre_translate(x, y); @@ -80,8 +81,9 @@ fn render_group( let mut mask = mask; let mut storage; if group.clips { - let w = group.frame.size.x.to_f32(); - let h = group.frame.size.y.to_f32(); + let size = group.frame.size(); + let w = size.x.to_f32(); + let h = size.y.to_f32(); if let Some(path) = sk::Rect::from_xywh(0.0, 0.0, w, h) .map(sk::PathBuilder::from_rect) .and_then(|path| path.transform(ts)) diff --git a/src/frame.rs b/src/frame.rs index 934c3f22e..628d8c5b9 100644 --- a/src/frame.rs +++ b/src/frame.rs @@ -17,38 +17,65 @@ use crate::util::{EcoString, MaybeShared}; #[derive(Default, Clone, Eq, PartialEq)] pub struct Frame { /// The size of the frame. - pub size: Size, + size: Size, /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. - pub baseline: Option, - /// The elements composing this layout. - pub elements: Vec<(Point, Element)>, + baseline: Option, /// The semantic role of the frame. role: Option, + /// The elements composing this layout. + elements: Vec<(Point, Element)>, } +/// Accessors and setters. impl Frame { /// Create a new, empty frame. + /// + /// Panics the size is not finite. #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); Self { size, baseline: None, - elements: vec![], role: None, + elements: vec![], } } + /// The size of the frame. + pub fn size(&self) -> Size { + self.size + } + + /// The size of the frame, mutably. + pub fn size_mut(&mut self) -> &mut Size { + &mut self.size + } + + /// Set the size of the frame. + pub fn set_size(&mut self, size: Size) { + self.size = size; + } + + /// The width of the frame. + pub fn width(&self) -> Length { + self.size.x + } + + /// The height of the frame. + pub fn height(&self) -> Length { + self.size.y + } + /// The baseline of the frame. pub fn baseline(&self) -> Length { self.baseline.unwrap_or(self.size.y) } - /// The layer the next item will be added on. This corresponds to the number - /// of elements in the frame. - pub fn layer(&self) -> usize { - self.elements.len() + /// Set the frame's baseline from the top. + pub fn set_baseline(&mut self, baseline: Length) { + self.baseline = Some(baseline); } /// The role of the frame. @@ -56,9 +83,36 @@ impl Frame { self.role } - /// Whether the frame has comparatively few elements. - pub fn is_light(&self) -> bool { - self.elements.len() <= 5 + /// An iterator over the elements inside this frame alongside their + /// positions relative to the top-left of the frame. + pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> { + self.elements.iter() + } + + /// Recover the text inside of the frame and its children. + pub fn text(&self) -> EcoString { + let mut text = EcoString::new(); + for (_, element) in &self.elements { + match element { + Element::Text(content) => { + for glyph in &content.glyphs { + text.push(glyph.c); + } + } + Element::Group(group) => text.push_str(&group.frame.text()), + _ => {} + } + } + text + } +} + +/// Inserting elements and subframes. +impl Frame { + /// The layer the next item will be added on. This corresponds to the number + /// of elements in the frame. + pub fn layer(&self) -> usize { + self.elements.len() } /// Add an element at a position in the foreground. @@ -66,6 +120,14 @@ impl Frame { self.elements.push((pos, element)); } + /// Insert an element at the given layer in the frame. + /// + /// This panics if the layer is greater than the number of layers present. + #[track_caller] + pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { + self.elements.insert(layer, (pos, element)); + } + /// Add a frame. /// /// Automatically decides whether to inline the frame or to include it as a @@ -105,11 +167,17 @@ impl Frame { } } - /// Insert an element at the given layer in the frame. - /// - /// This panics if the layer is greater than the number of layers present. - pub fn insert(&mut self, layer: usize, pos: Point, element: Element) { - self.elements.insert(layer, (pos, element)); + /// Whether the frame has comparatively few elements. + fn is_light(&self) -> bool { + self.elements.len() <= 5 + } +} + +/// Modify the frame. +impl Frame { + /// Remove all elements from the frame. + pub fn clear(&mut self) { + self.elements.clear(); } /// Resize the frame to a new size, distributing new space according to the @@ -137,11 +205,6 @@ impl Frame { } } - /// Arbitrarily transform the contents of the frame. - pub fn transform(&mut self, transform: Transform) { - self.group(|g| g.transform = transform); - } - /// Apply the given role to the frame if it doesn't already have one. pub fn apply_role(&mut self, role: Role) { if self.role.map_or(true, Role::is_weak) { @@ -149,13 +212,23 @@ impl Frame { } } + /// Link the whole frame to a resource. + pub fn link(&mut self, dest: Destination) { + self.push(Point::zero(), Element::Link(dest, self.size)); + } + + /// Arbitrarily transform the contents of the frame. + pub fn transform(&mut self, transform: Transform) { + self.group(|g| g.transform = transform); + } + /// Clip the contents of a frame to its size. pub fn clip(&mut self) { self.group(|g| g.clips = true); } /// Wrap the frame's contents in a group and modify that group with `f`. - pub fn group(&mut self, f: F) + fn group(&mut self, f: F) where F: FnOnce(&mut Group), { @@ -165,28 +238,6 @@ impl Frame { wrapper.push(Point::zero(), Element::Group(group)); *self = wrapper; } - - /// Link the whole frame to a resource. - pub fn link(&mut self, dest: Destination) { - self.push(Point::zero(), Element::Link(dest, self.size)); - } - - /// Recover the text inside of the frame and its children. - pub fn text(&self) -> EcoString { - let mut text = EcoString::new(); - for (_, element) in &self.elements { - match element { - Element::Text(content) => { - for glyph in &content.glyphs { - text.push(glyph.c); - } - } - Element::Group(group) => text.push_str(&group.frame.text()), - _ => {} - } - } - text - } } impl Debug for Frame { diff --git a/src/library/graphics/hide.rs b/src/library/graphics/hide.rs index 4ba5e023b..c969ef760 100644 --- a/src/library/graphics/hide.rs +++ b/src/library/graphics/hide.rs @@ -22,11 +22,7 @@ impl Layout for HideNode { // Clear the frames. for frame in &mut frames { - *frame = Arc::new({ - let mut empty = Frame::new(frame.size); - empty.baseline = frame.baseline; - empty - }); + Arc::make_mut(frame).clear(); } Ok(frames) diff --git a/src/library/graphics/shape.rs b/src/library/graphics/shape.rs index 82eb2d9d9..6c315c24f 100644 --- a/src/library/graphics/shape.rs +++ b/src/library/graphics/shape.rs @@ -104,7 +104,7 @@ impl Layout for ShapeNode { let target = regions.expand.select(regions.first, Size::zero()); target.x.max(target.y) } else { - let size = frames[0].size; + let size = frames[0].size(); let desired = size.x.max(size.y); desired.min(regions.first.x).min(regions.first.y) }; @@ -146,8 +146,8 @@ impl Layout for ShapeNode { } }; - let outset = styles.get(Self::OUTSET).relative_to(frame.size); - let size = frame.size + outset.sum_by_axis(); + let outset = styles.get(Self::OUTSET).relative_to(frame.size()); + let size = frame.size() + outset.sum_by_axis(); let radius = styles .get(Self::RADIUS) diff --git a/src/library/graphics/transform.rs b/src/library/graphics/transform.rs index 9fcf7ebb4..7176a683d 100644 --- a/src/library/graphics/transform.rs +++ b/src/library/graphics/transform.rs @@ -33,7 +33,7 @@ impl Layout for MoveNode { let delta = self.delta.resolve(styles); for frame in &mut frames { - let delta = delta.zip(frame.size).map(|(d, s)| d.relative_to(s)); + let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); Arc::make_mut(frame).translate(delta.to_point()); } @@ -94,7 +94,7 @@ impl Layout for TransformNode { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let Spec { x, y } = origin.zip(frame.size).map(|(o, s)| o.position(s)); + let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s)); let transform = Transform::translate(x, y) .pre_concat(self.transform) .pre_concat(Transform::translate(-x, -y)); diff --git a/src/library/layout/align.rs b/src/library/layout/align.rs index c0a7d16c2..c5adcf9f9 100644 --- a/src/library/layout/align.rs +++ b/src/library/layout/align.rs @@ -47,7 +47,7 @@ impl Layout for AlignNode { for (region, frame) in regions.iter().zip(&mut frames) { // Align in the target size. The target size depends on whether we // should expand. - let target = regions.expand.select(region, frame.size); + let target = regions.expand.select(region, frame.size()); let aligns = self .aligns .map(|align| align.resolve(styles)) diff --git a/src/library/layout/columns.rs b/src/library/layout/columns.rs index 4c8422612..b9e308f2a 100644 --- a/src/library/layout/columns.rs +++ b/src/library/layout/columns.rs @@ -80,10 +80,10 @@ impl Layout for ColumnsNode { }; if !regions.expand.y { - output.size.y.set_max(frame.size.y); + output.size_mut().y.set_max(frame.height()); } - let width = frame.size.x; + let width = frame.width(); let x = if dir.is_positive() { cursor } else { diff --git a/src/library/layout/flow.rs b/src/library/layout/flow.rs index f779c8b14..b6844f552 100644 --- a/src/library/layout/flow.rs +++ b/src/library/layout/flow.rs @@ -189,7 +189,7 @@ impl FlowLayouter { } // Grow our size, shrink the region and save the frame for later. - let size = frame.size; + let size = frame.size(); self.used.y += size.y; self.used.x.set_max(size.x); self.regions.first.y -= size.y; @@ -231,10 +231,10 @@ impl FlowLayouter { } FlowItem::Frame(frame, aligns) => { ruler = ruler.max(aligns.y); - let x = aligns.x.position(size.x - frame.size.x); + let x = aligns.x.position(size.x - frame.width()); let y = offset + ruler.position(size.y - self.used.y); let pos = Point::new(x, y); - offset += frame.size.y; + offset += frame.height(); output.push_frame(pos, frame); } FlowItem::Placed(frame) => { diff --git a/src/library/layout/grid.rs b/src/library/layout/grid.rs index 2d6eb2596..c04913a1c 100644 --- a/src/library/layout/grid.rs +++ b/src/library/layout/grid.rs @@ -304,7 +304,7 @@ impl<'a> GridLayouter<'a> { } let frame = node.layout(self.ctx, &pod, self.styles)?.remove(0); - resolved.set_max(frame.size.x); + resolved.set_max(frame.width()); } } @@ -376,7 +376,7 @@ impl<'a> GridLayouter<'a> { let mut sizes = node .layout(self.ctx, &pod, self.styles)? .into_iter() - .map(|frame| frame.size.y); + .map(|frame| frame.height()); self.ctx.pins.unfreeze(); // For each region, we want to know the maximum height any @@ -432,7 +432,7 @@ impl<'a> GridLayouter<'a> { let frame = self.layout_single_row(resolved, y)?; // Skip to fitting region. - let height = frame.size.y; + let height = frame.height(); while !self.regions.first.y.fits(height) && !self.regions.in_last() { self.finish_region()?; @@ -533,8 +533,8 @@ impl<'a> GridLayouter<'a> { /// Push a row frame into the current region. fn push_row(&mut self, frame: Frame) { - self.regions.first.y -= frame.size.y; - self.used.y += frame.size.y; + self.regions.first.y -= frame.height(); + self.used.y += frame.height(); self.lrows.push(Row::Frame(frame)); } @@ -562,7 +562,7 @@ impl<'a> GridLayouter<'a> { } }; - let height = frame.size.y; + let height = frame.height(); output.push_frame(pos, frame); pos.y += height; } diff --git a/src/library/layout/pad.rs b/src/library/layout/pad.rs index 97b760e18..29a72588f 100644 --- a/src/library/layout/pad.rs +++ b/src/library/layout/pad.rs @@ -40,13 +40,13 @@ impl Layout for PadNode { for frame in &mut frames { // Apply the padding inversely such that the grown size padded // yields the frame's size. - let padded = grow(frame.size, padding); + 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. let frame = Arc::make_mut(frame); - frame.size = padded; + frame.set_size(padded); frame.translate(offset); } diff --git a/src/library/layout/page.rs b/src/library/layout/page.rs index c2fbaba0b..e22bb4f7a 100644 --- a/src/library/layout/page.rs +++ b/src/library/layout/page.rs @@ -106,7 +106,7 @@ impl PageNode { // Realize overlays. for frame in &mut frames { - let size = frame.size; + let size = frame.size(); let pad = padding.resolve(styles).relative_to(size); let pw = size.x - pad.left - pad.right; let py = size.y - pad.bottom; diff --git a/src/library/layout/stack.rs b/src/library/layout/stack.rs index 9c2cbccd4..5d3e17865 100644 --- a/src/library/layout/stack.rs +++ b/src/library/layout/stack.rs @@ -201,7 +201,7 @@ impl<'a> StackLayouter<'a> { } // Grow our size, shrink the region and save the frame for later. - let size = frame.size.to_gen(self.axis); + let size = frame.size().to_gen(self.axis); self.used.main += size.main; self.used.cross.set_max(size.cross); *self.regions.first.get_mut(self.axis) -= size.main; @@ -248,7 +248,7 @@ impl<'a> StackLayouter<'a> { // Align along the block axis. let parent = size.get(self.axis); - let child = frame.size.get(self.axis); + let child = frame.size().get(self.axis); let block = ruler.position(parent - self.used.main) + if self.dir.is_positive() { cursor diff --git a/src/library/math/rex.rs b/src/library/math/rex.rs index 47de4b3a6..930829dec 100644 --- a/src/library/math/rex.rs +++ b/src/library/math/rex.rs @@ -66,8 +66,8 @@ impl Layout for RexNode { let mut backend = FrameBackend { frame: { let mut frame = Frame::new(size); + frame.set_baseline(baseline); frame.apply_role(Role::Formula); - frame.baseline = Some(baseline); frame }, baseline, diff --git a/src/library/text/par.rs b/src/library/text/par.rs index 41246b001..7a656a30e 100644 --- a/src/library/text/par.rs +++ b/src/library/text/par.rs @@ -336,7 +336,7 @@ impl<'a> Item<'a> { match self { Self::Text(shaped) => shaped.width, Self::Absolute(v) => *v, - Self::Frame(frame) => frame.size.x, + Self::Frame(frame) => frame.width(), Self::Fractional(_) | Self::Repeat(_, _) | Self::Pin(_) => Length::zero(), } } @@ -1071,7 +1071,7 @@ fn stack( // Stack the lines into one frame per region. for line in lines { let frame = commit(p, ctx, line, ®ions, width)?; - let height = frame.size.y; + let height = frame.size().y; while !regions.first.y.fits(height) && !regions.in_last() { finished.push(Arc::new(output)); @@ -1082,11 +1082,11 @@ fn stack( } if !first { - output.size.y += p.leading; + output.size_mut().y += p.leading; } - let pos = Point::with_y(output.size.y); - output.size.y += height; + let pos = Point::with_y(output.height()); + output.size_mut().y += height; output.push_frame(pos, frame); regions.first.y -= height + p.leading; @@ -1156,9 +1156,9 @@ fn commit( let mut frames = vec![]; for item in reordered { let mut push = |offset: &mut Length, frame: MaybeShared| { - let width = frame.size.x; + let width = frame.width(); top.set_max(frame.baseline()); - bottom.set_max(frame.size.y - frame.baseline()); + bottom.set_max(frame.size().y - frame.baseline()); frames.push((*offset, frame)); *offset += width; }; @@ -1179,23 +1179,24 @@ fn commit( } Item::Repeat(node, styles) => { let before = offset; - let width = Fraction::one().share(fr, remaining); - let size = Size::new(width, regions.base.y); + let fill = Fraction::one().share(fr, remaining); + let size = Size::new(fill, regions.base.y); let pod = Regions::one(size, regions.base, Spec::new(false, false)); let frame = node.layout(ctx, &pod, *styles)?.remove(0); - let count = (width / frame.size.x).floor(); - let remaining = width % frame.size.x; + let width = frame.width(); + let count = (fill / width).floor(); + let remaining = fill % width; let apart = remaining / (count - 1.0); if count == 1.0 { offset += p.align.position(remaining); } - if frame.size.x > Length::zero() { + if width > Length::zero() { for _ in 0 .. (count as usize).min(1000) { push(&mut offset, MaybeShared::Shared(frame.clone())); offset += apart; } } - offset = before + width; + offset = before + fill; } Item::Pin(idx) => { let mut frame = Frame::new(Size::zero()); @@ -1212,7 +1213,7 @@ fn commit( let size = Size::new(width, top + bottom); let mut output = Frame::new(size); - output.baseline = Some(top); + output.set_baseline(top); // Construct the line's frame. for (offset, frame) in frames { diff --git a/src/library/text/shaping.rs b/src/library/text/shaping.rs index 591abb5ea..bb88836cb 100644 --- a/src/library/text/shaping.rs +++ b/src/library/text/shaping.rs @@ -84,7 +84,7 @@ impl<'a> ShapedText<'a> { let mut offset = Length::zero(); let mut frame = Frame::new(size); - frame.baseline = Some(top); + frame.set_baseline(top); let shift = self.styles.get(TextNode::BASELINE); let lang = self.styles.get(TextNode::LANG); @@ -103,7 +103,7 @@ impl<'a> ShapedText<'a> { id: glyph.glyph_id, x_advance: glyph.x_advance + if glyph.is_justifiable() { - frame.size.x += justification; + frame.size_mut().x += justification; Em::from_length(justification, self.size) } else { Em::zero() @@ -120,6 +120,7 @@ impl<'a> ShapedText<'a> { fill, glyphs, }; + let text_layer = frame.layer(); let width = text.width(); diff --git a/src/model/layout.rs b/src/model/layout.rs index d8cf1cab7..c971197aa 100644 --- a/src/model/layout.rs +++ b/src/model/layout.rs @@ -366,7 +366,7 @@ impl Layout for SizedNode { // Ensure frame size matches regions size if expansion is on. let frame = &mut frames[0]; - let target = regions.expand.select(regions.first, frame.size); + let target = regions.expand.select(regions.first, frame.size()); Arc::make_mut(frame).resize(target, Align::LEFT_TOP); Ok(frames) @@ -391,7 +391,7 @@ impl Layout for FillNode { ) -> TypResult>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Geometry::Rect(frame.size).filled(self.fill); + let shape = Geometry::Rect(frame.size()).filled(self.fill); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) @@ -416,7 +416,7 @@ impl Layout for StrokeNode { ) -> TypResult>> { let mut frames = self.child.layout(ctx, regions, styles)?; for frame in &mut frames { - let shape = Geometry::Rect(frame.size).stroked(self.stroke); + let shape = Geometry::Rect(frame.size()).stroked(self.stroke); Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape)); } Ok(frames) diff --git a/src/model/locate.rs b/src/model/locate.rs index 495203aab..bda12ae9a 100644 --- a/src/model/locate.rs +++ b/src/model/locate.rs @@ -299,7 +299,7 @@ fn locate_in_frame( frame: &Frame, ts: Transform, ) { - for &(pos, ref element) in &frame.elements { + for &(pos, ref element) in frame.elements() { match element { Element::Group(group) => { let ts = ts diff --git a/tests/typeset.rs b/tests/typeset.rs index 452219a43..341a04c0a 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -538,8 +538,8 @@ fn render(ctx: &mut Context, frames: &[Arc]) -> sk::Pixmap { .iter() .map(|frame| { let limit = Length::cm(100.0); - if frame.size.x > limit || frame.size.y > limit { - panic!("overlarge frame: {:?}", frame.size); + if frame.width() > limit || frame.height() > limit { + panic!("overlarge frame: {:?}", frame.size()); } typst::export::render(ctx, frame, pixel_per_pt) }) @@ -579,7 +579,7 @@ fn render_links( ctx: &Context, frame: &Frame, ) { - for (pos, element) in &frame.elements { + for (pos, element) in frame.elements() { let ts = ts.pre_translate(pos.x.to_pt() as f32, pos.y.to_pt() as f32); match *element { Element::Group(ref group) => {