From 989d170dc7318ca3cbaa5b76760eb14f4e6a8605 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 28 Nov 2022 12:40:16 +0100 Subject: [PATCH] Fragments --- library/src/graphics/hide.rs | 18 +++-- library/src/graphics/image.rs | 12 ++-- library/src/graphics/line.rs | 14 ++-- library/src/graphics/shape.rs | 16 +++-- library/src/layout/align.rs | 16 ++--- library/src/layout/columns.rs | 14 ++-- library/src/layout/container.rs | 24 ++++--- library/src/layout/flow.rs | 24 +++---- library/src/layout/grid.rs | 37 +++++----- library/src/layout/mod.rs | 60 ++++------------- library/src/layout/pad.rs | 14 ++-- library/src/layout/page.rs | 10 +-- library/src/layout/place.rs | 14 ++-- library/src/layout/stack.rs | 18 ++--- library/src/layout/transform.rs | 52 +++++++------- library/src/math/mod.rs | 10 +-- library/src/math/tex.rs | 5 +- library/src/prelude.rs | 2 +- library/src/shared/ext.rs | 28 ++++---- library/src/structure/document.rs | 3 +- library/src/structure/list.rs | 10 +-- library/src/structure/table.rs | 10 +-- library/src/text/par.rs | 32 ++++----- src/doc.rs | 108 ++++++++++++++++++++---------- src/export/pdf/mod.rs | 2 +- src/export/pdf/outline.rs | 33 ++++----- src/export/pdf/page.rs | 23 +------ tests/ref/graphics/line.png | Bin 3472 -> 1562 bytes tests/typ/graphics/line.typ | 30 +++++---- 29 files changed, 318 insertions(+), 321 deletions(-) diff --git a/library/src/graphics/hide.rs b/library/src/graphics/hide.rs index 3a21c2c70..64cbee64d 100644 --- a/library/src/graphics/hide.rs +++ b/library/src/graphics/hide.rs @@ -4,22 +4,26 @@ use crate::prelude::*; #[derive(Debug, Hash)] pub struct HideNode(pub Content); -#[node(LayoutInline)] +#[node(Layout, Inline)] impl HideNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } } -impl LayoutInline for HideNode { - fn layout_inline( +impl Layout for HideNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { - let mut frame = self.0.layout_inline(world, styles, regions)?; - frame.clear(); - Ok(frame) + ) -> SourceResult { + let mut fragment = self.0.layout(world, styles, regions)?; + for frame in &mut fragment { + frame.clear(); + } + Ok(fragment) } } + +impl Inline for HideNode {} diff --git a/library/src/graphics/image.rs b/library/src/graphics/image.rs index de3384df1..2c58496cc 100644 --- a/library/src/graphics/image.rs +++ b/library/src/graphics/image.rs @@ -9,7 +9,7 @@ use crate::text::LinkNode; #[derive(Debug, Hash)] pub struct ImageNode(pub Image); -#[node(LayoutInline)] +#[node(Layout, Inline)] impl ImageNode { /// How the image should adjust itself to a given area. pub const FIT: ImageFit = ImageFit::Cover; @@ -37,13 +37,13 @@ impl ImageNode { } } -impl LayoutInline for ImageNode { - fn layout_inline( +impl Layout for ImageNode { + fn layout( &self, _: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { + ) -> SourceResult { let pxw = self.0.width() as f64; let pxh = self.0.height() as f64; let px_ratio = pxw / pxh; @@ -94,10 +94,12 @@ impl LayoutInline for ImageNode { frame.link(url.clone()); } - Ok(frame) + Ok(Fragment::frame(frame)) } } +impl Inline for ImageNode {} + /// How an image should adjust itself to a given area. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum ImageFit { diff --git a/library/src/graphics/line.rs b/library/src/graphics/line.rs index 11f0be32c..8acf5bb6e 100644 --- a/library/src/graphics/line.rs +++ b/library/src/graphics/line.rs @@ -9,7 +9,7 @@ pub struct LineNode { delta: Axes>, } -#[node(LayoutInline)] +#[node(Layout, Inline)] impl LineNode { /// How to stroke the line. #[property(resolve, fold)] @@ -36,13 +36,13 @@ impl LineNode { } } -impl LayoutInline for LineNode { - fn layout_inline( +impl Layout for LineNode { + fn layout( &self, _: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { + ) -> SourceResult { let stroke = styles.get(Self::STROKE).unwrap_or_default(); let origin = self @@ -58,11 +58,13 @@ impl LayoutInline for LineNode { .map(|(l, b)| l.relative_to(b)); let target = regions.expand.select(regions.first, Size::zero()); - let mut frame = Frame::new(target); + let mut frame = Frame::new(target); let shape = Geometry::Line(delta.to_point()).stroked(stroke); frame.push(origin.to_point(), Element::Shape(shape)); - Ok(frame) + Ok(Fragment::frame(frame)) } } + +impl Inline for LineNode {} diff --git a/library/src/graphics/shape.rs b/library/src/graphics/shape.rs index 4c9fec07d..114182e5f 100644 --- a/library/src/graphics/shape.rs +++ b/library/src/graphics/shape.rs @@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode; /// A ellipse with optional content. pub type EllipseNode = ShapeNode; -#[node(LayoutInline)] +#[node(Layout, Inline)] impl ShapeNode { /// How to fill the shape. pub const FILL: Option = None; @@ -72,13 +72,13 @@ impl ShapeNode { } } -impl LayoutInline for ShapeNode { - fn layout_inline( +impl Layout for ShapeNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { + ) -> SourceResult { let mut frame; if let Some(child) = &self.0 { let mut inset = styles.get(Self::INSET); @@ -90,7 +90,7 @@ impl LayoutInline for ShapeNode { let child = child.clone().padded(inset.map(|side| side.map(Length::from))); let mut pod = Regions::one(regions.first, regions.base, regions.expand); - frame = child.layout_inline(world, styles, &pod)?; + frame = child.layout(world, styles, &pod)?.into_frame(); // Relayout with full expansion into square region to make sure // the result is really a square or circle. @@ -106,7 +106,7 @@ impl LayoutInline for ShapeNode { pod.first = Size::splat(length); pod.expand = Axes::splat(true); - frame = child.layout_inline(world, styles, &pod)?; + frame = child.layout(world, styles, &pod)?.into_frame(); } } else { // The default size that a shape takes on if it has no child and @@ -165,10 +165,12 @@ impl LayoutInline for ShapeNode { frame.link(url.clone()); } - Ok(frame) + Ok(Fragment::frame(frame)) } } +impl Inline for ShapeNode {} + /// A category of shape. pub type ShapeKind = usize; diff --git a/library/src/layout/align.rs b/library/src/layout/align.rs index 10a4a2ed3..d8b6d92e8 100644 --- a/library/src/layout/align.rs +++ b/library/src/layout/align.rs @@ -10,14 +10,14 @@ pub struct AlignNode { pub child: Content, } -#[node(LayoutBlock)] +#[node(Layout)] impl AlignNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let aligns: Axes> = args.find()?.unwrap_or_default(); let body: Content = args.expect("body")?; if let Axes { x: Some(x), y: None } = aligns { - if !body.has::() { + if !body.has::() || body.has::() { return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x))); } } @@ -26,13 +26,13 @@ impl AlignNode { } } -impl LayoutBlock for AlignNode { - fn layout_block( +impl Layout for AlignNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { // The child only needs to expand along an axis if there's no alignment. let mut pod = regions.clone(); pod.expand &= self.aligns.as_ref().map(Option::is_none); @@ -44,8 +44,8 @@ impl LayoutBlock for AlignNode { } // Layout the child. - let mut frames = self.child.layout_block(world, styles.chain(&map), &pod)?; - for (region, frame) in regions.iter().zip(&mut frames) { + let mut fragment = self.child.layout(world, styles.chain(&map), &pod)?; + for (region, frame) in regions.iter().zip(&mut fragment) { // Align in the target size. The target size depends on whether we // should expand. let target = regions.expand.select(region, frame.size()); @@ -57,6 +57,6 @@ impl LayoutBlock for AlignNode { frame.resize(target, aligns); } - Ok(frames) + Ok(fragment) } } diff --git a/library/src/layout/columns.rs b/library/src/layout/columns.rs index b18ba49fe..257cc62f6 100644 --- a/library/src/layout/columns.rs +++ b/library/src/layout/columns.rs @@ -11,7 +11,7 @@ pub struct ColumnsNode { pub child: Content, } -#[node(LayoutBlock)] +#[node(Layout)] impl ColumnsNode { /// The size of the gutter space between each column. #[property(resolve)] @@ -26,17 +26,17 @@ impl ColumnsNode { } } -impl LayoutBlock for ColumnsNode { - fn layout_block( +impl Layout for ColumnsNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { // Separating the infinite space into infinite columns does not make // much sense. if !regions.first.x.is_finite() { - return self.child.layout_block(world, styles, regions); + return self.child.layout(world, styles, regions); } // Determine the width of the gutter and each column. @@ -58,7 +58,7 @@ impl LayoutBlock for ColumnsNode { }; // Layout the children. - let mut frames = self.child.layout_block(world, styles, &pod)?.into_iter(); + let mut frames = self.child.layout(world, styles, &pod)?.into_iter(); let mut finished = vec![]; let dir = styles.get(TextNode::DIR); @@ -94,7 +94,7 @@ impl LayoutBlock for ColumnsNode { finished.push(output); } - Ok(finished) + Ok(Fragment::frames(finished)) } } diff --git a/library/src/layout/container.rs b/library/src/layout/container.rs index b299c0fcc..1c1f87629 100644 --- a/library/src/layout/container.rs +++ b/library/src/layout/container.rs @@ -10,7 +10,7 @@ pub struct BoxNode { pub child: Content, } -#[node(LayoutInline)] +#[node(Layout, Inline)] impl BoxNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let width = args.named("width")?; @@ -20,13 +20,13 @@ impl BoxNode { } } -impl LayoutInline for BoxNode { - fn layout_inline( +impl Layout for BoxNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { + ) -> SourceResult { // The "pod" is the region into which the child will be layouted. let pod = { // Resolve the sizing to a concrete size. @@ -47,21 +47,23 @@ impl LayoutInline for BoxNode { }; // Layout the child. - let mut frame = self.child.layout_inline(world, styles, &pod)?; + let mut frame = self.child.layout(world, styles, &pod)?.into_frame(); // Ensure frame size matches regions size if expansion is on. let target = regions.expand.select(regions.first, frame.size()); frame.resize(target, Align::LEFT_TOP); - Ok(frame) + Ok(Fragment::frame(frame)) } } +impl Inline for BoxNode {} + /// A block-level container that places content into a separate flow. #[derive(Debug, Hash)] pub struct BlockNode(pub Content); -#[node(LayoutBlock)] +#[node(Layout)] impl BlockNode { /// The spacing between the previous and this block. #[property(skip)] @@ -87,13 +89,13 @@ impl BlockNode { } } -impl LayoutBlock for BlockNode { - fn layout_block( +impl Layout for BlockNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { - self.0.layout_block(world, styles, regions) + ) -> SourceResult { + self.0.layout(world, styles, regions) } } diff --git a/library/src/layout/flow.rs b/library/src/layout/flow.rs index 3338da097..fd3e5fc70 100644 --- a/library/src/layout/flow.rs +++ b/library/src/layout/flow.rs @@ -11,23 +11,23 @@ use crate::text::ParNode; #[derive(Hash)] pub struct FlowNode(pub StyleVec); -#[node(LayoutBlock)] +#[node(Layout)] impl FlowNode {} -impl LayoutBlock for FlowNode { - fn layout_block( +impl Layout for FlowNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let mut layouter = FlowLayouter::new(regions); for (child, map) in self.0.iter() { let styles = styles.chain(&map); if let Some(&node) = child.to::() { layouter.layout_spacing(node.amount, styles); - } else if child.has::() { + } else if child.has::() { layouter.layout_block(world, child, styles)?; } else if child.is::() { layouter.finish_region(); @@ -136,7 +136,7 @@ impl FlowLayouter { // aligned later. if let Some(placed) = block.to::() { if placed.out_of_flow() { - let frame = block.layout_block(world, styles, &self.regions)?.remove(0); + let frame = block.layout(world, styles, &self.regions)?.into_frame(); self.items.push(FlowItem::Placed(frame)); return Ok(()); } @@ -166,9 +166,9 @@ impl FlowLayouter { } // Layout the block itself. - let frames = block.layout_block(world, chained, &self.regions)?; - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { + let fragment = block.layout(world, chained, &self.regions)?; + let len = fragment.len(); + for (i, frame) in fragment.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. let size = frame.size(); self.used.y += size.y; @@ -234,8 +234,8 @@ impl FlowLayouter { self.finished.push(output); } - /// Finish layouting and return the resulting frames. - fn finish(mut self) -> Vec { + /// Finish layouting and return the resulting fragment. + fn finish(mut self) -> Fragment { if self.expand.y { while !self.regions.backlog.is_empty() { self.finish_region(); @@ -243,6 +243,6 @@ impl FlowLayouter { } self.finish_region(); - self.finished + Fragment::frames(self.finished) } } diff --git a/library/src/layout/grid.rs b/library/src/layout/grid.rs index 4cbef4214..470b1f3bb 100644 --- a/library/src/layout/grid.rs +++ b/library/src/layout/grid.rs @@ -13,7 +13,7 @@ pub struct GridNode { pub cells: Vec, } -#[node(LayoutBlock)] +#[node(Layout)] impl GridNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let TrackSizings(columns) = args.named("columns")?.unwrap_or_default(); @@ -33,13 +33,13 @@ impl GridNode { } } -impl LayoutBlock for GridNode { - fn layout_block( +impl Layout for GridNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { // Prepare grid layout by unifying content and gutter tracks. let layouter = GridLayouter::new( world, @@ -222,7 +222,7 @@ impl<'a> GridLayouter<'a> { } /// Determines the columns sizes and then layouts the grid row-by-row. - fn layout(mut self) -> SourceResult> { + fn layout(mut self) -> SourceResult { self.measure_columns()?; for y in 0..self.rows.len() { @@ -243,7 +243,7 @@ impl<'a> GridLayouter<'a> { } self.finish_region()?; - Ok(self.finished) + Ok(Fragment::frames(self.finished)) } /// Determine all column sizes. @@ -320,8 +320,7 @@ impl<'a> GridLayouter<'a> { v.resolve(self.styles).relative_to(self.regions.base.y); } - let frame = - cell.layout_block(self.world, self.styles, &pod)?.remove(0); + let frame = cell.layout(self.world, self.styles, &pod)?.into_frame(); resolved.set_max(frame.width()); } } @@ -391,7 +390,7 @@ impl<'a> GridLayouter<'a> { } let mut sizes = cell - .layout_block(self.world, self.styles, &pod)? + .layout(self.world, self.styles, &pod)? .into_iter() .map(|frame| frame.height()); @@ -429,9 +428,9 @@ impl<'a> GridLayouter<'a> { } // Layout into multiple regions. - let frames = self.layout_multi_row(&resolved, y)?; - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { + let fragment = self.layout_multi_row(&resolved, y)?; + let len = fragment.len(); + for (i, frame) in fragment.into_iter().enumerate() { self.push_row(frame); if i + 1 < len { self.finish_region()?; @@ -480,7 +479,7 @@ impl<'a> GridLayouter<'a> { .select(self.regions.base, size); let pod = Regions::one(size, base, Axes::splat(true)); - let frame = cell.layout_block(self.world, self.styles, &pod)?.remove(0); + let frame = cell.layout(self.world, self.styles, &pod)?.into_frame(); output.push_frame(pos, frame); } @@ -491,11 +490,7 @@ impl<'a> GridLayouter<'a> { } /// Layout a row spanning multiple regions. - fn layout_multi_row( - &mut self, - heights: &[Abs], - y: usize, - ) -> SourceResult> { + fn layout_multi_row(&mut self, heights: &[Abs], y: usize) -> SourceResult { // Prepare frames. let mut outputs: Vec<_> = heights .iter() @@ -520,8 +515,8 @@ impl<'a> GridLayouter<'a> { } // Push the layouted frames into the individual output frames. - let frames = cell.layout_block(self.world, self.styles, &pod)?; - for (output, frame) in outputs.iter_mut().zip(frames) { + let fragment = cell.layout(self.world, self.styles, &pod)?; + for (output, frame) in outputs.iter_mut().zip(fragment) { output.push_frame(pos, frame); } } @@ -529,7 +524,7 @@ impl<'a> GridLayouter<'a> { pos.x += rcol; } - Ok(outputs) + Ok(Fragment::frames(outputs)) } /// Push a row frame into the current region. diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index e63a072e6..3481a6bdf 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -29,7 +29,6 @@ use std::mem; use comemo::Tracked; use typed_arena::Arena; use typst::diag::SourceResult; -use typst::doc::Frame; use typst::geom::*; use typst::model::{ applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain, @@ -70,72 +69,37 @@ impl LayoutRoot for Content { } } -/// Block-level layout. +/// Layout into regions. #[capability] -pub trait LayoutBlock { +pub trait Layout { /// Layout into one frame per region. - fn layout_block( + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult>; + ) -> SourceResult; } -impl LayoutBlock for Content { +impl Layout for Content { #[comemo::memoize] - fn layout_block( + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let scratch = Scratch::default(); let (realized, styles) = realize_block(world, &scratch, self, styles)?; let barrier = Style::Barrier(realized.id()); let styles = styles.chain_one(&barrier); - realized - .with::() - .unwrap() - .layout_block(world, styles, regions) + realized.with::().unwrap().layout(world, styles, regions) } } /// Inline-level layout. #[capability] -pub trait LayoutInline { - /// Layout into a single frame. - fn layout_inline( - &self, - world: Tracked, - styles: StyleChain, - regions: &Regions, - ) -> SourceResult; -} - -impl LayoutInline for Content { - #[comemo::memoize] - fn layout_inline( - &self, - world: Tracked, - styles: StyleChain, - regions: &Regions, - ) -> SourceResult { - assert!(regions.backlog.is_empty()); - assert!(regions.last.is_none()); - - if self.has::() && !applicable(self, styles) { - let barrier = Style::Barrier(self.id()); - let styles = styles.chain_one(&barrier); - return self - .with::() - .unwrap() - .layout_inline(world, styles, regions); - } - - Ok(self.layout_block(world, styles, regions)?.remove(0)) - } -} +pub trait Inline: Layout {} /// A sequence of regions to layout into. #[derive(Debug, Clone, Hash)] @@ -255,7 +219,7 @@ fn realize_block<'a>( content: &'a Content, styles: StyleChain<'a>, ) -> SourceResult<(Content, StyleChain<'a>)> { - if content.has::() && !applicable(content, styles) { + if content.has::() && !applicable(content, styles) { return Ok((content.clone(), styles)); } @@ -497,7 +461,7 @@ impl<'a> FlowBuilder<'a> { return true; } - if content.has::() { + if content.has::() { let is_tight_list = if let Some(node) = content.to::() { node.tight } else if let Some(node) = content.to::() { @@ -542,7 +506,7 @@ impl<'a> ParBuilder<'a> { || content.is::() || content.is::() || content.is::() - || content.has::() + || content.has::() { self.0.push(content.clone(), styles); return true; diff --git a/library/src/layout/pad.rs b/library/src/layout/pad.rs index 4389d990f..c688dd478 100644 --- a/library/src/layout/pad.rs +++ b/library/src/layout/pad.rs @@ -9,7 +9,7 @@ pub struct PadNode { pub child: Content, } -#[node(LayoutBlock)] +#[node(Layout)] impl PadNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let all = args.named("rest")?.or(args.find()?); @@ -25,19 +25,19 @@ impl PadNode { } } -impl LayoutBlock for PadNode { - fn layout_block( +impl Layout for PadNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { // Layout child into padded regions. let padding = self.padding.resolve(styles); let pod = regions.map(|size| shrink(size, padding)); - let mut frames = self.child.layout_block(world, styles, &pod)?; + let mut fragment = self.child.layout(world, styles, &pod)?; - for frame in &mut frames { + 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); @@ -49,7 +49,7 @@ impl LayoutBlock for PadNode { frame.translate(offset); } - Ok(frames) + Ok(fragment) } } diff --git a/library/src/layout/page.rs b/library/src/layout/page.rs index 42db02c39..9fe608ee9 100644 --- a/library/src/layout/page.rs +++ b/library/src/layout/page.rs @@ -60,7 +60,7 @@ impl PageNode { world: Tracked, mut page: usize, styles: StyleChain, - ) -> SourceResult> { + ) -> SourceResult { // When one of the lengths is infinite the page fits its content along // that axis. let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf()); @@ -97,7 +97,7 @@ impl PageNode { // Layout the child. let regions = Regions::repeat(size, size, size.map(Abs::is_finite)); - let mut frames = child.layout_block(world, styles, ®ions)?; + let mut fragment = child.layout(world, styles, ®ions)?; let header = styles.get(Self::HEADER); let footer = styles.get(Self::FOOTER); @@ -105,7 +105,7 @@ impl PageNode { let background = styles.get(Self::BACKGROUND); // Realize overlays. - for frame in &mut frames { + for frame in &mut fragment { let size = frame.size(); let pad = padding.resolve(styles).relative_to(size); let pw = size.x - pad.left - pad.right; @@ -118,7 +118,7 @@ impl PageNode { ] { if let Some(content) = marginal.resolve(world, page)? { let pod = Regions::one(area, area, Axes::splat(true)); - let sub = content.layout_block(world, styles, &pod)?.remove(0); + let sub = content.layout(world, styles, &pod)?.into_frame(); if std::ptr::eq(marginal, background) { frame.prepend_frame(pos, sub); } else { @@ -130,7 +130,7 @@ impl PageNode { page += 1; } - Ok(frames) + Ok(fragment) } } diff --git a/library/src/layout/place.rs b/library/src/layout/place.rs index af3130738..215b5b9f2 100644 --- a/library/src/layout/place.rs +++ b/library/src/layout/place.rs @@ -5,7 +5,7 @@ use crate::prelude::*; #[derive(Debug, Hash)] pub struct PlaceNode(pub Content); -#[node(LayoutBlock, Behave)] +#[node(Layout, Behave)] impl PlaceNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start))); @@ -16,13 +16,13 @@ impl PlaceNode { } } -impl LayoutBlock for PlaceNode { - fn layout_block( +impl Layout for PlaceNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let out_of_flow = self.out_of_flow(); // The pod is the base area of the region because for absolute @@ -33,14 +33,14 @@ impl LayoutBlock for PlaceNode { Regions::one(regions.base, regions.base, expand) }; - let mut frames = self.0.layout_block(world, styles, &pod)?; + let mut frame = self.0.layout(world, styles, &pod)?.into_frame(); // If expansion is off, zero all sizes so that we don't take up any // space in our parent. Otherwise, respect the expand settings. let target = regions.expand.select(regions.first, Size::zero()); - frames[0].resize(target, Align::LEFT_TOP); + frame.resize(target, Align::LEFT_TOP); - Ok(frames) + Ok(Fragment::frame(frame)) } } diff --git a/library/src/layout/stack.rs b/library/src/layout/stack.rs index c935d9715..7de1d34a6 100644 --- a/library/src/layout/stack.rs +++ b/library/src/layout/stack.rs @@ -15,7 +15,7 @@ pub struct StackNode { pub children: Vec, } -#[node(LayoutBlock)] +#[node(Layout)] impl StackNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self { @@ -27,13 +27,13 @@ impl StackNode { } } -impl LayoutBlock for StackNode { - fn layout_block( +impl Layout for StackNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let mut layouter = StackLayouter::new(self.dir, regions, styles); // Spacing to insert before the next block. @@ -196,9 +196,9 @@ impl<'a> StackLayouter<'a> { self.dir.start().into() }); - let frames = block.layout_block(world, styles, &self.regions)?; - let len = frames.len(); - for (i, frame) in frames.into_iter().enumerate() { + let fragment = block.layout(world, styles, &self.regions)?; + let len = fragment.len(); + for (i, frame) in fragment.into_iter().enumerate() { // Grow our size, shrink the region and save the frame for later. let size = frame.size(); let size = match self.axis { @@ -276,9 +276,9 @@ impl<'a> StackLayouter<'a> { } /// Finish layouting and return the resulting frames. - fn finish(mut self) -> Vec { + fn finish(mut self) -> Fragment { self.finish_region(); - self.finished + Fragment::frames(self.finished) } } diff --git a/library/src/layout/transform.rs b/library/src/layout/transform.rs index f09b4e65d..cfc4ac83e 100644 --- a/library/src/layout/transform.rs +++ b/library/src/layout/transform.rs @@ -11,7 +11,7 @@ pub struct MoveNode { pub child: Content, } -#[node(LayoutInline)] +#[node(Layout, Inline)] impl MoveNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { let dx = args.named("dx")?.unwrap_or_default(); @@ -24,21 +24,25 @@ impl MoveNode { } } -impl LayoutInline for MoveNode { - fn layout_inline( +impl Layout for MoveNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { - let mut frame = self.child.layout_inline(world, styles, regions)?; - let delta = self.delta.resolve(styles); - let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); - frame.translate(delta.to_point()); - Ok(frame) + ) -> SourceResult { + let mut fragment = self.child.layout(world, styles, regions)?; + for frame in &mut fragment { + let delta = self.delta.resolve(styles); + let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s)); + frame.translate(delta.to_point()); + } + Ok(fragment) } } +impl Inline for MoveNode {} + /// Transform content without affecting layout. #[derive(Debug, Hash)] pub struct TransformNode { @@ -54,7 +58,7 @@ pub type RotateNode = TransformNode; /// Scale content without affecting layout. pub type ScaleNode = TransformNode; -#[node(LayoutInline)] +#[node(Layout, Inline)] impl TransformNode { /// The origin of the transformation. #[property(resolve)] @@ -78,26 +82,28 @@ impl TransformNode { } } -impl LayoutInline for TransformNode { - fn layout_inline( +impl Layout for TransformNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { - let mut frame = self.child.layout_inline(world, styles, regions)?; - - let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); - let Axes { 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)); - frame.transform(transform); - - Ok(frame) + ) -> SourceResult { + let mut fragment = self.child.layout(world, styles, regions)?; + for frame in &mut fragment { + let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON); + let Axes { 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)); + frame.transform(transform); + } + Ok(fragment) } } +impl Inline for TransformNode {} + /// Kinds of transformations. /// /// The move transformation is handled separately. diff --git a/library/src/math/mod.rs b/library/src/math/mod.rs index 3b1d66e9a..7136c8b94 100644 --- a/library/src/math/mod.rs +++ b/library/src/math/mod.rs @@ -19,7 +19,7 @@ pub struct MathNode { pub display: bool, } -#[node(Show, LayoutInline, Texify)] +#[node(Show, Layout, Inline, Texify)] impl MathNode { fn field(&self, name: &str) -> Option { match name { @@ -48,17 +48,19 @@ impl Show for MathNode { } } -impl LayoutInline for MathNode { - fn layout_inline( +impl Layout for MathNode { + fn layout( &self, world: Tracked, styles: StyleChain, _: &Regions, - ) -> SourceResult { + ) -> SourceResult { layout_tex(world, &self.texify(), self.display, styles) } } +impl Inline for MathNode {} + impl Texify for MathNode { fn texify(&self) -> EcoString { self.children.iter().map(Texify::texify).collect() diff --git a/library/src/math/tex.rs b/library/src/math/tex.rs index a85bab189..5f332f3ce 100644 --- a/library/src/math/tex.rs +++ b/library/src/math/tex.rs @@ -39,7 +39,7 @@ pub fn layout_tex( tex: &str, display: bool, styles: StyleChain, -) -> SourceResult { +) -> SourceResult { // Load the font. let variant = variant(styles); let mut font = None; @@ -98,7 +98,8 @@ pub fn layout_tex( // Render into the frame. renderer.render(&layout, &mut backend); - Ok(backend.frame) + + Ok(Fragment::frame(backend.frame)) } /// A ReX rendering backend that renders into a frame. diff --git a/library/src/prelude.rs b/library/src/prelude.rs index bc0ec31d4..87fc8e0dc 100644 --- a/library/src/prelude.rs +++ b/library/src/prelude.rs @@ -27,6 +27,6 @@ pub use typst::util::{format_eco, EcoString}; pub use typst::World; #[doc(no_inline)] -pub use crate::layout::{LayoutBlock, LayoutInline, Regions}; +pub use crate::layout::{Inline, Layout, Regions}; #[doc(no_inline)] pub use crate::shared::{Behave, Behaviour, ContentExt, StyleMapExt}; diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index f90260ad4..54a1598a2 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -99,22 +99,22 @@ struct FillNode { child: Content, } -#[node(LayoutBlock)] +#[node(Layout)] impl FillNode {} -impl LayoutBlock for FillNode { - fn layout_block( +impl Layout for FillNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { - let mut frames = self.child.layout_block(world, styles, regions)?; - for frame in &mut frames { + ) -> SourceResult { + let mut fragment = self.child.layout(world, styles, regions)?; + for frame in &mut fragment { let shape = Geometry::Rect(frame.size()).filled(self.fill); frame.prepend(Point::zero(), Element::Shape(shape)); } - Ok(frames) + Ok(fragment) } } @@ -127,21 +127,21 @@ struct StrokeNode { child: Content, } -#[node(LayoutBlock)] +#[node(Layout)] impl StrokeNode {} -impl LayoutBlock for StrokeNode { - fn layout_block( +impl Layout for StrokeNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { - let mut frames = self.child.layout_block(world, styles, regions)?; - for frame in &mut frames { + ) -> SourceResult { + let mut fragment = self.child.layout(world, styles, regions)?; + for frame in &mut fragment { let shape = Geometry::Rect(frame.size()).stroked(self.stroke); frame.prepend(Point::zero(), Element::Shape(shape)); } - Ok(frames) + Ok(fragment) } } diff --git a/library/src/structure/document.rs b/library/src/structure/document.rs index 2e5761e04..e52c92ad2 100644 --- a/library/src/structure/document.rs +++ b/library/src/structure/document.rs @@ -26,7 +26,8 @@ impl LayoutRoot for DocumentNode { let mut pages = vec![]; for (page, map) in self.0.iter() { let number = 1 + pages.len(); - pages.extend(page.layout(world, number, styles.chain(map))?); + let fragment = page.layout(world, number, styles.chain(map))?; + pages.extend(fragment); } Ok(Document { diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs index 6bfddd2e8..b51284a8f 100644 --- a/library/src/structure/list.rs +++ b/library/src/structure/list.rs @@ -18,7 +18,7 @@ pub type EnumNode = ListNode; /// A description list. pub type DescNode = ListNode; -#[node(LayoutBlock)] +#[node(Layout)] impl ListNode { /// How the list is labelled. #[property(referenced)] @@ -75,13 +75,13 @@ impl ListNode { } } -impl LayoutBlock for ListNode { - fn layout_block( +impl Layout for ListNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let mut cells = vec![]; let mut number = 1; @@ -137,7 +137,7 @@ impl LayoutBlock for ListNode { gutter: Axes::with_y(vec![gutter.into()]), cells, } - .layout_block(world, styles, regions) + .layout(world, styles, regions) } } diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs index 4dd14cddd..bb900f3dd 100644 --- a/library/src/structure/table.rs +++ b/library/src/structure/table.rs @@ -12,7 +12,7 @@ pub struct TableNode { pub cells: Vec, } -#[node(LayoutBlock)] +#[node(Layout)] impl TableNode { /// How to fill the cells. #[property(referenced)] @@ -50,13 +50,13 @@ impl TableNode { } } -impl LayoutBlock for TableNode { - fn layout_block( +impl Layout for TableNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { let fill = styles.get(Self::FILL); let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); let padding = styles.get(Self::PADDING); @@ -89,7 +89,7 @@ impl LayoutBlock for TableNode { gutter: self.gutter.clone(), cells, } - .layout_block(world, styles, regions) + .layout(world, styles, regions) } } diff --git a/library/src/text/par.rs b/library/src/text/par.rs index 4c22c0348..9dc878739 100644 --- a/library/src/text/par.rs +++ b/library/src/text/par.rs @@ -15,7 +15,7 @@ use crate::prelude::*; #[derive(Hash)] pub struct ParNode(pub StyleVec); -#[node(LayoutBlock)] +#[node(Layout)] impl ParNode { /// The indent the first line of a consecutive paragraph should have. #[property(resolve)] @@ -43,13 +43,13 @@ impl ParNode { } } -impl LayoutBlock for ParNode { - fn layout_block( +impl Layout for ParNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult> { + ) -> SourceResult { // Collect all text into one string for BiDi analysis. let (text, segments) = collect(self, &styles); @@ -130,24 +130,26 @@ impl Unlabellable for ParbreakNode {} #[derive(Debug, Hash)] pub struct RepeatNode(pub Content); -#[node(LayoutInline)] +#[node(Layout, Inline)] impl RepeatNode { fn construct(_: &Vm, args: &mut Args) -> SourceResult { Ok(Self(args.expect("body")?).pack()) } } -impl LayoutInline for RepeatNode { - fn layout_inline( +impl Layout for RepeatNode { + fn layout( &self, world: Tracked, styles: StyleChain, regions: &Regions, - ) -> SourceResult { - self.0.layout_inline(world, styles, regions) + ) -> SourceResult { + self.0.layout(world, styles, regions) } } +impl Inline for RepeatNode {} + /// Range of a substring of text. type Range = std::ops::Range; @@ -405,7 +407,7 @@ fn collect<'a>( .find_map(|child| { if child.is::() || child.is::() { Some(true) - } else if child.has::() { + } else if child.has::() { Some(false) } else { None @@ -460,7 +462,7 @@ fn collect<'a>( } else if let Some(&node) = child.to::() { full.push(SPACING_REPLACE); Segment::Spacing(node.amount) - } else if child.has::() { + } else if child.has::() { full.push(NODE_REPLACE); Segment::Inline(child) } else { @@ -530,7 +532,7 @@ fn prepare<'a>( } else { let size = Size::new(regions.first.x, regions.base.y); let pod = Regions::one(size, regions.base, Axes::splat(false)); - let mut frame = inline.layout_inline(world, styles, &pod)?; + let mut frame = inline.layout(world, styles, &pod)?.into_frame(); frame.translate(Point::with_y(styles.get(TextNode::BASELINE))); items.push(Item::Frame(frame)); } @@ -1011,7 +1013,7 @@ fn line<'a>( } /// Combine layouted lines into one frame per region. -fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult> { +fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult { // Determine the paragraph's width: Full width of the region if we // should expand or there's fractional spacing, fit-to-width otherwise. let mut width = regions.first.x; @@ -1050,7 +1052,7 @@ fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult, } +/// A partial layout result. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Fragment(Vec); + +impl Fragment { + /// Create a fragment from a single frame. + pub fn frame(frame: Frame) -> Self { + Self(vec![frame]) + } + + /// Create a fragment from multiple frames. + pub fn frames(frames: Vec) -> Self { + Self(frames) + } + + /// The number of frames in the fragment. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Extract the first and only frame. + /// + /// Panics if there are multiple frames. + #[track_caller] + pub fn into_frame(self) -> Frame { + assert_eq!(self.0.len(), 1, "expected exactly one frame"); + self.0.into_iter().next().unwrap() + } + + /// Iterate over the contained frames. + pub fn iter(&self) -> std::slice::Iter { + self.0.iter() + } + + /// Iterate over the contained frames. + pub fn iter_mut(&mut self) -> std::slice::IterMut { + self.0.iter_mut() + } +} + +impl IntoIterator for Fragment { + type Item = Frame; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a Fragment { + type Item = &'a Frame; + type IntoIter = std::slice::Iter<'a, Frame>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a> IntoIterator for &'a mut Fragment { + type Item = &'a mut Frame; + type IntoIter = std::slice::IterMut<'a, Frame>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + /// A finished layout with elements at fixed positions. #[derive(Default, Clone, Eq, PartialEq)] pub struct Frame { @@ -39,8 +106,6 @@ pub struct Frame { /// The baseline of the frame measured from the top. If this is `None`, the /// frame's implicit baseline is at the bottom. baseline: Option, - /// The semantic role of the frame. - role: Option, /// The elements composing this layout. elements: Arc>, } @@ -53,12 +118,7 @@ impl Frame { #[track_caller] pub fn new(size: Size) -> Self { assert!(size.is_finite()); - Self { - size, - baseline: None, - role: None, - elements: Arc::new(vec![]), - } + Self { size, baseline: None, elements: Arc::new(vec![]) } } /// The size of the frame. @@ -96,11 +156,6 @@ impl Frame { self.baseline = Some(baseline); } - /// The role of the frame. - pub fn role(&self) -> Option { - self.role - } - /// 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)> { @@ -125,7 +180,7 @@ impl Frame { } } -/// Inserting elements and subframes. +/// Insert elements and subframes. impl Frame { /// The layer the next item will be added on. This corresponds to the number /// of elements in the frame. @@ -141,7 +196,7 @@ impl Frame { /// Add a frame at a position in the foreground. /// /// Automatically decides whether to inline the frame or to include it as a - /// group based on the number of elements in and the role of the frame. + /// group based on the number of elements in it. pub fn push_frame(&mut self, pos: Point, frame: Frame) { if self.should_inline(&frame) { self.inline(self.layer(), pos, frame); @@ -185,8 +240,7 @@ impl Frame { /// Whether the given frame should be inlined. fn should_inline(&self, frame: &Frame) -> bool { - (self.elements.is_empty() || frame.elements.len() <= 5) - && frame.role().map_or(true, |role| role.is_weak()) + self.elements.is_empty() || frame.elements.len() <= 5 } /// Inline a frame at the given layer. @@ -294,10 +348,6 @@ impl Frame { impl Debug for Frame { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if let Some(role) = self.role { - write!(f, "{role:?} ")?; - } - f.debug_list() .entries(self.elements.iter().map(|(_, element)| element)) .finish() @@ -503,8 +553,8 @@ impl Location { } } -/// A semantic role of a frame. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +/// Standard semantic roles. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Role { /// A paragraph. Paragraph, @@ -542,13 +592,3 @@ pub enum Role { /// A page foreground. Foreground, } - -impl Role { - /// Whether the role describes a generic element and is not very - /// descriptive. - pub fn is_weak(self) -> bool { - // In Typst, all text is in a paragraph, so paragraph isn't very - // descriptive. - matches!(self, Self::Paragraph | Self::GenericBlock | Self::GenericInline) - } -} diff --git a/src/export/pdf/mod.rs b/src/export/pdf/mod.rs index 7e5a3c064..8f9d96374 100644 --- a/src/export/pdf/mod.rs +++ b/src/export/pdf/mod.rs @@ -12,7 +12,7 @@ use std::hash::Hash; use pdf_writer::types::Direction; use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr}; -use self::outline::{Heading, HeadingNode}; +use self::outline::HeadingNode; use self::page::Page; use crate::doc::{Document, Lang, Metadata}; use crate::font::Font; diff --git a/src/export/pdf/outline.rs b/src/export/pdf/outline.rs index add167b42..e7a356c13 100644 --- a/src/export/pdf/outline.rs +++ b/src/export/pdf/outline.rs @@ -4,43 +4,34 @@ use super::{AbsExt, PdfContext, RefExt}; use crate::geom::{Abs, Point}; use crate::util::EcoString; -/// A heading that can later be linked in the outline panel. +/// A heading in the outline panel. #[derive(Debug, Clone)] -pub struct Heading { +pub struct HeadingNode { pub content: EcoString, pub level: usize, pub position: Point, pub page: Ref, -} - -/// A node in the outline tree. -#[derive(Debug, Clone)] -pub struct HeadingNode { - pub heading: Heading, pub children: Vec, } impl HeadingNode { - pub fn leaf(heading: Heading) -> Self { - HeadingNode { heading, children: Vec::new() } - } - pub fn len(&self) -> usize { 1 + self.children.iter().map(Self::len).sum::() } - pub fn insert(&mut self, other: Heading, level: usize) -> bool { - if level >= other.level { + #[allow(unused)] + pub fn try_insert(&mut self, child: Self, level: usize) -> bool { + if level >= child.level { return false; } - if let Some(child) = self.children.last_mut() { - if child.insert(other.clone(), level + 1) { + if let Some(last) = self.children.last_mut() { + if last.try_insert(child.clone(), level + 1) { return true; } } - self.children.push(Self::leaf(other)); + self.children.push(child); true } } @@ -74,10 +65,10 @@ pub fn write_outline_item( outline.count(-(node.children.len() as i32)); } - outline.title(TextStr(&node.heading.content)); - outline.dest_direct().page(node.heading.page).xyz( - node.heading.position.x.to_f32(), - (node.heading.position.y + Abs::pt(3.0)).to_f32(), + outline.title(TextStr(&node.content)); + outline.dest_direct().page(node.page).xyz( + node.position.x.to_f32(), + (node.position.y + Abs::pt(3.0)).to_f32(), None, ); diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index 7c4794253..fc714e7ac 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -2,10 +2,8 @@ use pdf_writer::types::{ActionType, AnnotationType, ColorSpaceOperand}; use pdf_writer::writers::ColorSpace; use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str}; -use super::{ - deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB, -}; -use crate::doc::{Destination, Element, Frame, Group, Role, Text}; +use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB}; +use crate::doc::{Destination, Element, Frame, Group, Text}; use crate::font::Font; use crate::geom::{ self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke, @@ -281,23 +279,6 @@ impl PageContext<'_, '_> { /// Encode a frame into the content stream. fn write_frame(ctx: &mut PageContext, frame: &Frame) { - if let Some(Role::Heading { level, outlined: true }) = frame.role() { - let heading = Heading { - position: Point::new(ctx.state.transform.tx, ctx.state.transform.ty), - content: frame.text(), - page: ctx.page_ref, - level: level.get(), - }; - - if let Some(last) = ctx.parent.heading_tree.last_mut() { - if !last.insert(heading.clone(), 1) { - ctx.parent.heading_tree.push(HeadingNode::leaf(heading)) - } - } else { - ctx.parent.heading_tree.push(HeadingNode::leaf(heading)) - } - } - for &(pos, ref element) in frame.elements() { let x = pos.x.to_f32(); let y = pos.y.to_f32(); diff --git a/tests/ref/graphics/line.png b/tests/ref/graphics/line.png index 7858b5c9a734ef9f957b4467de3de98ec9bf26f3..4a73ccd733fe97431160d899f59b05d53134c475 100644 GIT binary patch literal 1562 zcmeAS@N?(olHy`uVBq!ia0y~yU}OPe6Aose$lo=4rvf=U1AIbU|NsC0@87?_fB*jZ z^XJ#EUq62Q{PykpmoHyGfBy2}O6Z`fb-MjC|p1p^6?>V$>$G)xG_io<0XXEBw8#e7+zj4R94cphQ-?o1J z`nBt}ty#Nu^_nd!S8ZCcYUA>i8<#F$zhv3E#Y@*NShRZn!c}wTEuTGi*~~dhXUtkW zZN|bW(-uscGOvH)?7j)Jx_YK}_DpNlJb_~vgU%~hP=YMoPyfyyy~pns`RXiw9N948V_~gR4q=MMQ{FsEi znE1S?n4HL%?C_|}(1?tX@O1y+WWS&!-@rr5L?F}dwY9J zTYn21KXYqeQ!5`MGY>;kcLNi59Rp_r0|R}1eGMH4bsc+EEn6jZYeh9H1yxIVWpinH z6Dc`k30XsN8ACB?0})9*A#oi+F>L`+ZGI6gK4DEBL3M5cHDDxuZT|I=fq_NF)5S5Q z;?~<+;lUz~B5e;{9WR=8TQ)N?8go6HQ!K`6sMyrOshq?o5wP#)(YO*_g+*H*I{1Fy z_q;3f)g7DA&8)Zcm68^*uqZSzFmZs1OhuJXIS#<|B7i|8&Jf)6_u4x{jz+}~R&Wgn ztw5Gj6vvS{3JnLKvWNZ){@5&u#VDZhK7wt3-1N>1C84)3* zXv^~SCid_E2ddK>fjr&M`PXF@7QHHdU$$1TuGg;O%=XygmTYOOXFs=P_igyr=4EAj z+tgj-#npe4=e>tDD`pGlFa-)w}5iL^fJ#&t_SLWw`R(cZ$b0R&I)de znWo}f_5R{I&O?1m{~CXut@857ntzKoEjPSl>Ym{%lj(idyZp=I_Y2mh)yb5%KP-)i z{k#5tO;mx+vfbb2bN05Lb)FY5|6gO{~*|LJwH&sTh3$ByIspo&1-LLb*r#;om5hH@(%x`D-mxGuCqLJ^X{aOox%QU zW*)CJza#(y=*_$$nt24m4TK4?FMSOXt=DcFR-L% z<-%kUxMP6!18w#=u5r!KXzzV+g4Y_ RVqkH>;OXk;vd$@?2>>ZQtfl|} literal 3472 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 W`gL~-0=IkiF=Ipcg-U(