diff --git a/Cargo.toml b/Cargo.toml index 15eea562c..d1f7f9463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,27 +4,31 @@ version = "0.1.0" authors = ["The Typst Project Developers"] edition = "2018" -[lib] -bench = false - [workspace] members = ["main"] -[profile.dev.package."*"] -opt-level = 2 - -[dependencies] -fontdock = { path = "../fontdock", default-features = false } -tide = { path = "../tide" } -ttf-parser = "0.8.2" -unicode-xid = "0.2" -serde = { version = "1", features = ["derive"], optional = true } - [features] default = ["serialize", "fs"] serialize = ["serde"] fs = ["fontdock/fs"] +[lib] +bench = false + +[profile.dev.package."*"] +opt-level = 2 + +[profile.release] +lto = true + +[dependencies] +fontdock = { path = "../fontdock", default-features = false } +kurbo = "0.6.3" +tide = { path = "../tide" } +ttf-parser = "0.8.2" +unicode-xid = "0.2" +serde = { version = "1", features = ["derive"], optional = true } + [dev-dependencies] criterion = "0.3" futures-executor = "0.3" diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 52200e661..a63640558 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -9,15 +9,10 @@ use typstc::parse::parse; use typstc::Typesetter; const FONT_DIR: &str = "fonts"; - -// 28 not too dense lines. const COMA: &str = include_str!("../tests/coma.typ"); fn parsing_benchmark(c: &mut Criterion) { - c.bench_function("parse-coma-28-lines", |b| b.iter(|| parse(COMA))); - - let long = COMA.repeat(100); - c.bench_function("parse-coma-2800-lines", |b| b.iter(|| parse(&long))); + c.bench_function("parse-coma", |b| b.iter(|| parse(COMA))); } fn typesetting_benchmark(c: &mut Criterion) { @@ -29,15 +24,9 @@ fn typesetting_benchmark(c: &mut Criterion) { let loader = FontLoader::new(Box::new(provider), descriptors); let loader = Rc::new(RefCell::new(loader)); let typesetter = Typesetter::new(loader.clone()); - - c.bench_function("typeset-coma-28-lines", |b| { + c.bench_function("typeset-coma", |b| { b.iter(|| futures_executor::block_on(typesetter.typeset(COMA))) }); - - let long = COMA.repeat(100); - c.bench_function("typeset-coma-2800-lines", |b| { - b.iter(|| futures_executor::block_on(typesetter.typeset(&long))) - }); } criterion_group!(benches, parsing_benchmark, typesetting_benchmark); diff --git a/src/export/pdf.rs b/src/export/pdf.rs index b87b31811..1bed21881 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -111,8 +111,8 @@ impl<'a, W: Write> PdfExporter<'a, W> { let rect = Rect::new( 0.0, 0.0, - Length::raw(page.size.x).as_pt() as f32, - Length::raw(page.size.y).as_pt() as f32, + Length::raw(page.size.width).as_pt() as f32, + Length::raw(page.size.height).as_pt() as f32, ); self.writer.write_obj( @@ -152,7 +152,7 @@ impl<'a, W: Write> PdfExporter<'a, W> { } let x = Length::raw(pos.x).as_pt(); - let y = Length::raw(page.size.y - pos.y - size).as_pt(); + let y = Length::raw(page.size.height - pos.y - size).as_pt(); text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); text.tj(shaped.encode_glyphs_be()); } diff --git a/src/geom.rs b/src/geom.rs index f897c08fb..6aa875f14 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -1,209 +1,161 @@ //! Geometrical types. -use std::fmt::{self, Debug, Formatter}; -use std::ops::*; +#[doc(no_inline)] +pub use kurbo::*; -use crate::layout::primitive::*; +use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis}; -/// A value in two dimensions. -#[derive(Default, Copy, Clone, Eq, PartialEq)] -pub struct Value2 { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} +/// Additional methods for [sizes]. +/// +/// [sizes]: ../../kurbo/struct.Size.html +pub trait SizeExt { + /// Return the primary component of this specialized size. + fn primary(self, sys: LayoutSystem) -> f64; -impl Value2 { - /// Create a new 2D-value from two values. - pub fn new(x: T, y: T) -> Self { - Self { x, y } - } + /// Borrow the primary component of this specialized size mutably. + fn primary_mut(&mut self, sys: LayoutSystem) -> &mut f64; - /// Create a new 2D-value with `x` set to a value and `y` to default. - pub fn with_x(x: T) -> Self - where - T: Default, - { - Self { x, y: T::default() } - } + /// Return the secondary component of this specialized size. + fn secondary(self, sys: LayoutSystem) -> f64; - /// Create a new 2D-value with `y` set to a value and `x` to default. - pub fn with_y(y: T) -> Self - where - T: Default, - { - Self { x: T::default(), y } - } + /// Borrow the secondary component of this specialized size mutably. + fn secondary_mut(&mut self, sys: LayoutSystem) -> &mut f64; - /// Create a 2D-value with `x` and `y` set to the same value `s`. - pub fn with_all(s: T) -> Self { - Self { x: s.clone(), y: s } - } - - /// Get the specificed component. - pub fn get(self, axis: SpecAxis) -> T { - match axis { - SpecAxis::Horizontal => self.x, - SpecAxis::Vertical => self.y, - } - } - - /// Borrow the specificed component mutably. - pub fn get_mut(&mut self, axis: SpecAxis) -> &mut T { - match axis { - SpecAxis::Horizontal => &mut self.x, - SpecAxis::Vertical => &mut self.y, - } - } - - /// Return the primary value of this specialized 2D-value. - pub fn primary(self, sys: LayoutSystem) -> T { - if sys.primary.axis() == SpecAxis::Horizontal { - self.x - } else { - self.y - } - } - - /// Borrow the primary value of this specialized 2D-value mutably. - pub fn primary_mut(&mut self, sys: LayoutSystem) -> &mut T { - if sys.primary.axis() == SpecAxis::Horizontal { - &mut self.x - } else { - &mut self.y - } - } - - /// Return the secondary value of this specialized 2D-value. - pub fn secondary(self, sys: LayoutSystem) -> T { - if sys.primary.axis() == SpecAxis::Horizontal { - self.y - } else { - self.x - } - } - - /// Borrow the secondary value of this specialized 2D-value mutably. - pub fn secondary_mut(&mut self, sys: LayoutSystem) -> &mut T { - if sys.primary.axis() == SpecAxis::Horizontal { - &mut self.y - } else { - &mut self.x - } - } - - /// Returns the generalized version of a `Size2D` dependent on the layouting + /// Returns the generalized version of a `Size` based on the layouting /// system, that is: /// - `x` describes the primary axis instead of the horizontal one. /// - `y` describes the secondary axis instead of the vertical one. - pub fn generalized(self, sys: LayoutSystem) -> Self { - match sys.primary.axis() { - SpecAxis::Horizontal => self, - SpecAxis::Vertical => Self { x: self.y, y: self.x }, - } - } + fn generalized(self, sys: LayoutSystem) -> Self; /// Returns the specialized version of this generalized Size2D (inverse to /// `generalized`). - pub fn specialized(self, sys: LayoutSystem) -> Self { - // In fact, generalized is its own inverse. For reasons of clarity - // at the call site, we still have this second function. - self.generalized(sys) - } - - /// Swap the `x` and `y` values. - pub fn swap(&mut self) { - std::mem::swap(&mut self.x, &mut self.y); - } -} - -impl Debug for Value2 { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_list().entry(&self.x).entry(&self.y).finish() - } -} - -/// A position or extent in 2-dimensional space. -pub type Size = Value2; - -impl Size { - /// The zeroed size. - pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; + fn specialized(self, sys: LayoutSystem) -> Self; /// Whether the given size fits into this one, that is, both coordinate /// values are smaller or equal. - pub fn fits(self, other: Self) -> bool { - self.x >= other.x && self.y >= other.y - } - - /// Return a size padded by the paddings of the given box. - pub fn padded(self, padding: Margins) -> Self { - Size { - x: self.x + padding.left + padding.right, - y: self.y + padding.top + padding.bottom, - } - } - - /// Return a size reduced by the paddings of the given box. - pub fn unpadded(self, padding: Margins) -> Self { - Size { - x: self.x - padding.left - padding.right, - y: self.y - padding.top - padding.bottom, - } - } + fn fits(self, other: Self) -> bool; /// The anchor position along the given axis for an item with the given /// alignment in a container with this size. /// /// This assumes the size to be generalized such that `x` corresponds to the /// primary axis. - pub fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Self { - Size { - x: anchor(self.x, align.primary, sys.primary), - y: anchor(self.y, align.secondary, sys.secondary), + fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point; +} + +impl SizeExt for Size { + fn primary(self, sys: LayoutSystem) -> f64 { + if sys.primary.axis() == SpecAxis::Horizontal { + self.width + } else { + self.height + } + } + + fn primary_mut(&mut self, sys: LayoutSystem) -> &mut f64 { + if sys.primary.axis() == SpecAxis::Horizontal { + &mut self.width + } else { + &mut self.height + } + } + + fn secondary(self, sys: LayoutSystem) -> f64 { + if sys.primary.axis() == SpecAxis::Horizontal { + self.height + } else { + self.width + } + } + + fn secondary_mut(&mut self, sys: LayoutSystem) -> &mut f64 { + if sys.primary.axis() == SpecAxis::Horizontal { + &mut self.height + } else { + &mut self.width + } + } + + fn generalized(self, sys: LayoutSystem) -> Self { + match sys.primary.axis() { + SpecAxis::Horizontal => self, + SpecAxis::Vertical => Self::new(self.height, self.width), + } + } + + fn specialized(self, sys: LayoutSystem) -> Self { + // In fact, generalized is its own inverse. For reasons of clarity + // at the call site, we still have this second function. + self.generalized(sys) + } + + fn fits(self, other: Self) -> bool { + self.width >= other.width && self.height >= other.height + } + + fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point { + fn length_anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { + match (dir.is_positive(), align) { + (true, GenAlign::Start) | (false, GenAlign::End) => 0.0, + (_, GenAlign::Center) => length / 2.0, + (true, GenAlign::End) | (false, GenAlign::Start) => length, + } + } + + Point::new( + length_anchor(self.width, align.primary, sys.primary), + length_anchor(self.height, align.secondary, sys.secondary), + ) + } +} + +/// Additional methods for [rectangles]. +/// +/// [rectangles]: ../../kurbo/struct.Rect.html +pub trait RectExt { + /// Get a mutable reference to the value for the specified direction at the + /// alignment. + /// + /// Center alignment is treated the same as origin alignment. + fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut f64; +} + +impl RectExt for Rect { + fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut f64 { + match if align == GenAlign::End { dir.inv() } else { dir } { + Dir::LTR => &mut self.x0, + Dir::TTB => &mut self.y0, + Dir::RTL => &mut self.x1, + Dir::BTT => &mut self.y1, } } } -fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { - match (dir.is_positive(), align) { - (true, GenAlign::Start) | (false, GenAlign::End) => 0.0, - (_, GenAlign::Center) => length / 2.0, - (true, GenAlign::End) | (false, GenAlign::Start) => length, - } -} - -impl Neg for Size { - type Output = Size; - - fn neg(self) -> Size { - Size { x: -self.x, y: -self.y } - } -} - -/// A value in four dimensions. +/// A generic container for `[left, top, right, bottom]` values. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] -pub struct Value4 { - /// The left extent. +pub struct Sides { + /// The value for the left side. pub left: T, - /// The top extent. + /// The value for the top side. pub top: T, - /// The right extent. + /// The value for the right side. pub right: T, - /// The bottom extent. + /// The value for the bottom side. pub bottom: T, } -impl Value4 { +impl Sides { /// Create a new box from four sizes. pub fn new(left: T, top: T, right: T, bottom: T) -> Self { - Value4 { left, top, right, bottom } + Self { left, top, right, bottom } } - /// Create a box with all four fields set to the same value `s`. - pub fn with_all(value: T) -> Self { - Value4 { + /// Create an instance with all four components set to the same `value`. + pub fn uniform(value: T) -> Self + where + T: Clone, + { + Self { left: value.clone(), top: value.clone(), right: value.clone(), @@ -215,105 +167,12 @@ impl Value4 { /// alignment. /// /// Center alignment is treated the same as origin alignment. - pub fn get_mut(&mut self, mut dir: Dir, align: GenAlign) -> &mut T { - if align == GenAlign::End { - dir = dir.inv(); - } - - match dir { + pub fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut T { + match if align == GenAlign::End { dir.inv() } else { dir } { Dir::LTR => &mut self.left, Dir::RTL => &mut self.right, Dir::TTB => &mut self.top, Dir::BTT => &mut self.bottom, } } - - /// Set all values to the given value. - pub fn set_all(&mut self, value: T) { - *self = Value4::with_all(value); - } } - -/// A length in four dimensions. -pub type Margins = Value4; - -impl Margins { - /// The zero margins. - pub const ZERO: Margins = Margins { - left: 0.0, - top: 0.0, - right: 0.0, - bottom: 0.0, - }; -} - -macro_rules! implement_traits { - ($ty:ident, $t:ident, $o:ident - reflexive {$( - ($tr:ident($tf:ident), $at:ident($af:ident), [$($f:ident),*]) - )*} - numbers { $(($w:ident: $($rest:tt)*))* } - ) => { - $(impl $tr for $ty { - type Output = $ty; - fn $tf($t, $o: $ty) -> $ty { - $ty { $($f: $tr::$tf($t.$f, $o.$f),)* } - } - } - - impl $at for $ty { - fn $af(&mut $t, $o: $ty) { $($at::$af(&mut $t.$f, $o.$f);)* } - })* - - $(implement_traits!(@$w f64, $ty $t $o $($rest)*);)* - }; - - (@front $num:ty, $ty:ident $t:ident $o:ident - $tr:ident($tf:ident), - [$($f:ident),*] - ) => { - impl $tr<$ty> for $num { - type Output = $ty; - fn $tf($t, $o: $ty) -> $ty { - $ty { $($f: $tr::$tf($t as f64, $o.$f),)* } - } - } - }; - - (@back $num:ty, $ty:ident $t:ident $o:ident - $tr:ident($tf:ident), $at:ident($af:ident), - [$($f:ident),*] - ) => { - impl $tr<$num> for $ty { - type Output = $ty; - fn $tf($t, $o: $num) -> $ty { - $ty { $($f: $tr::$tf($t.$f, $o as f64),)* } - } - } - - impl $at<$num> for $ty { - fn $af(&mut $t, $o: $num) { $($at::$af(&mut $t.$f, $o as f64);)* } - } - }; -} - -macro_rules! implement_size { - ($ty:ident($t:ident, $o:ident) [$($f:ident),*]) => { - implement_traits! { - $ty, $t, $o - - reflexive { - (Add(add), AddAssign(add_assign), [$($f),*]) - (Sub(sub), SubAssign(sub_assign), [$($f),*]) - } - - numbers { - (front: Mul(mul), [$($f),*]) - (back: Mul(mul), MulAssign(mul_assign), [$($f),*]) - (back: Div(div), DivAssign(div_assign), [$($f),*]) - } - } - }; -} - -implement_size! { Size(self, other) [x, y] } diff --git a/src/layout/elements.rs b/src/layout/elements.rs index dd148a242..b5f83bfe4 100644 --- a/src/layout/elements.rs +++ b/src/layout/elements.rs @@ -5,11 +5,11 @@ use std::fmt::{self, Debug, Formatter}; use fontdock::FaceId; use ttf_parser::GlyphId; -use crate::geom::Size; +use crate::geom::Point; /// A collection of absolutely positioned layout elements. #[derive(Debug, Default, Clone, PartialEq)] -pub struct LayoutElements(pub Vec<(Size, LayoutElement)>); +pub struct LayoutElements(pub Vec<(Point, LayoutElement)>); impl LayoutElements { /// Create an new empty collection. @@ -18,16 +18,15 @@ impl LayoutElements { } /// Add an element at a position. - pub fn push(&mut self, pos: Size, element: LayoutElement) { + pub fn push(&mut self, pos: Point, element: LayoutElement) { self.0.push((pos, element)); } - /// Add all elements of another collection, offsetting each by the given - /// `offset`. This can be used to place a sublayout at a position in another - /// layout. - pub fn extend_offset(&mut self, offset: Size, more: Self) { + /// Add all elements of another collection, placing them relative to the + /// given position. + pub fn push_elements(&mut self, pos: Point, more: Self) { for (subpos, element) in more.0 { - self.0.push((subpos + offset, element)); + self.0.push((pos + subpos.to_vec2(), element)); } } } diff --git a/src/layout/line.rs b/src/layout/line.rs index d8e97df36..86531f30d 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -95,14 +95,14 @@ impl LineLayouter { let usable = self.stack.usable().primary(sys); rest_run.usable = Some(match layout.align.primary { GenAlign::Start => unreachable!("start > x"), - GenAlign::Center => usable - 2.0 * self.run.size.x, - GenAlign::End => usable - self.run.size.x, + GenAlign::Center => usable - 2.0 * self.run.size.width, + GenAlign::End => usable - self.run.size.width, }); - rest_run.size.y = self.run.size.y; + rest_run.size.height = self.run.size.height; self.finish_line(); - self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard); + self.stack.add_spacing(-rest_run.size.height, SpacingKind::Hard); self.run = rest_run; } @@ -126,10 +126,10 @@ impl LineLayouter { } self.run.align = Some(layout.align); - self.run.layouts.push((self.run.size.x, layout)); + self.run.layouts.push((self.run.size.width, layout)); - self.run.size.x += size.x; - self.run.size.y = self.run.size.y.max(size.y); + self.run.size.width += size.width; + self.run.size.height = self.run.size.height.max(size.height); self.run.last_spacing = LastSpacing::None; } @@ -152,10 +152,10 @@ impl LineLayouter { // If there was another run already, override the stack's size. if let Some(primary) = self.run.usable { - usable.x = primary; + usable.width = primary; } - usable.x -= self.run.size.x; + usable.width -= self.run.size.width; usable } @@ -163,8 +163,8 @@ impl LineLayouter { pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { SpacingKind::Hard => { - spacing = spacing.min(self.usable().x); - self.run.size.x += spacing; + spacing = spacing.min(self.usable().width); + self.run.size.width += spacing; self.run.last_spacing = LastSpacing::Hard; } @@ -215,7 +215,7 @@ impl LineLayouter { /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); - *spaces[0].size.secondary_mut(self.ctx.sys) -= self.run.size.y; + *spaces[0].size.secondary_mut(self.ctx.sys) -= self.run.size.height; spaces } @@ -246,11 +246,11 @@ impl LineLayouter { for (offset, layout) in layouts { let x = match self.ctx.sys.primary.is_positive() { true => offset, - false => self.run.size.x - offset - layout.size.primary(self.ctx.sys), + false => self.run.size.width - offset - layout.size.primary(self.ctx.sys), }; - let pos = Size::with_x(x); - elements.extend_offset(pos, layout.elements); + let pos = Point::new(x, 0.0); + elements.push_elements(pos, layout.elements); } self.stack.add(BoxLayout { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index fcaab3723..11c03b0b7 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -10,9 +10,10 @@ mod tree; pub use primitive::*; pub use tree::layout_tree as layout; +use crate::geom::{Insets, Point, Rect, RectExt, Sides, Size, SizeExt}; + use crate::eval::Scope; use crate::font::SharedFontLoader; -use crate::geom::{Margins, Size}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::SynTree; @@ -67,22 +68,21 @@ pub struct LayoutSpace { /// The maximum size of the rectangle to layout into. pub size: Size, /// Padding that should be respected on each side. - pub padding: Margins, + pub insets: Insets, /// Whether to expand the size of the resulting layout to the full size of /// this space or to shrink it to fit the content. pub expansion: LayoutExpansion, } impl LayoutSpace { - /// The offset from the origin to the start of content, i.e. - /// `(padding.left, padding.top)`. - pub fn start(&self) -> Size { - Size::new(self.padding.left, self.padding.top) + /// The position of the padded start in the space. + pub fn start(&self) -> Point { + Point::new(-self.insets.x0, -self.insets.y0) } /// The actually usable area (size minus padding). pub fn usable(&self) -> Size { - self.size.unpadded(self.padding) + self.size + self.insets.size() } /// The inner layout space with size reduced by the padding, zero padding of @@ -90,7 +90,7 @@ impl LayoutSpace { pub fn inner(&self) -> Self { Self { size: self.usable(), - padding: Margins::ZERO, + insets: Insets::ZERO, expansion: LayoutExpansion::new(false, false), } } diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 0f42d4bb5..4de30a9eb 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -11,7 +11,6 @@ use super::elements::{LayoutElement, Shaped}; use super::BoxLayout as Layout; use super::*; use crate::font::FontLoader; -use crate::geom::Size; use crate::style::TextStyle; /// Shape text into a box. @@ -74,7 +73,7 @@ impl<'a> Shaper<'a> { // Flush the last buffered parts of the word. if !self.shaped.text.is_empty() { - let pos = Size::new(self.offset, 0.0); + let pos = Point::new(self.offset, 0.0); self.layout.elements.push(pos, LayoutElement::Text(self.shaped)); } @@ -97,9 +96,9 @@ impl<'a> Shaper<'a> { Shaped::new(FaceId::MAX, self.opts.style.font_size()), ); - let pos = Size::new(self.offset, 0.0); + let pos = Point::new(self.offset, 0.0); self.layout.elements.push(pos, LayoutElement::Text(shaped)); - self.offset = self.layout.size.x; + self.offset = self.layout.size.width; } self.shaped.face = index; @@ -107,9 +106,9 @@ impl<'a> Shaper<'a> { self.shaped.text.push(c); self.shaped.glyphs.push(glyph); - self.shaped.offsets.push(self.layout.size.x - self.offset); + self.shaped.offsets.push(self.layout.size.width - self.offset); - self.layout.size.x += char_width; + self.layout.size.width += char_width; } async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> { diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 3e63f5e51..a68fbac08 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -20,7 +20,6 @@ //! sentence in the second box. use super::*; -use crate::geom::Value4; /// Performs the stack layouting. pub struct StackLayouter { @@ -64,7 +63,7 @@ struct Space { /// Dictate which alignments for new boxes are still allowed and which /// require a new space to be started. For example, after an `End`-aligned /// item, no `Start`-aligned one can follow. - rulers: Value4, + rulers: Sides, /// The spacing state. This influences how new spacing is handled, e.g. hard /// spacing may override soft spacing. last_spacing: LastSpacing, @@ -127,7 +126,7 @@ impl StackLayouter { SpacingKind::Hard => { // Reduce the spacing such that it definitely fits. spacing = spacing.min(self.space.usable.secondary(self.ctx.sys)); - let size = Size::with_y(spacing); + let size = Size::new(0.0, spacing); self.update_metrics(size); self.space.layouts.push((self.ctx.sys, BoxLayout { @@ -161,15 +160,15 @@ impl StackLayouter { let mut size = self.space.size.generalized(sys); let mut extra = self.space.extra.generalized(sys); - size.x += (added.x - extra.x).max(0.0); - size.y += (added.y - extra.y).max(0.0); + size.width += (added.width - extra.width).max(0.0); + size.height += (added.height - extra.height).max(0.0); - extra.x = extra.x.max(added.x); - extra.y = (extra.y - added.y).max(0.0); + extra.width = extra.width.max(added.width); + extra.height = (extra.height - added.height).max(0.0); self.space.size = size.specialized(sys); self.space.extra = extra.specialized(sys); - *self.space.usable.secondary_mut(sys) -= added.y; + *self.space.usable.secondary_mut(sys) -= added.height; } /// Returns true if a space break is necessary. @@ -239,7 +238,7 @@ impl StackLayouter { let mut spaces = vec![LayoutSpace { size, - padding: Margins::ZERO, + insets: Insets::ZERO, expansion: LayoutExpansion::new(false, false), }]; @@ -253,7 +252,7 @@ impl StackLayouter { /// The remaining usable size. pub fn usable(&self) -> Size { self.space.usable - - Size::with_y(self.space.last_spacing.soft_or_zero()) + - Size::new(0.0, self.space.last_spacing.soft_or_zero()) .specialized(self.ctx.sys) } @@ -286,13 +285,13 @@ impl StackLayouter { let usable = space.usable(); if space.expansion.horizontal { - self.space.size.x = usable.x; + self.space.size.width = usable.width; } if space.expansion.vertical { - self.space.size.y = usable.y; + self.space.size.height = usable.height; } - let size = self.space.size.padded(space.padding); + let size = self.space.size - space.insets.size(); // ------------------------------------------------------------------ // // Step 2: Forward pass. Create a bounding box for each layout in which @@ -302,11 +301,11 @@ impl StackLayouter { let start = space.start(); let mut bounds = vec![]; - let mut bound = Margins { - left: start.x, - top: start.y, - right: start.x + self.space.size.x, - bottom: start.y + self.space.size.y, + let mut bound = Rect { + x0: start.x, + y0: start.y, + x1: start.x + self.space.size.width, + y1: start.y + self.space.size.height, }; for (sys, layout) in &self.space.layouts { @@ -340,8 +339,8 @@ impl StackLayouter { // is thus stored in `extent.y`. The primary extent is reset for // this new axis-aligned run. if rotation != sys.secondary.axis() { - extent.y = extent.x; - extent.x = 0.0; + extent.height = extent.width; + extent.width = 0.0; rotation = sys.secondary.axis(); } @@ -349,12 +348,12 @@ impl StackLayouter { // accumulated secondary extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. *bound.get_mut(sys.secondary, GenAlign::End) -= - sys.secondary.factor() * extent.y; + sys.secondary.factor() * extent.height; // Then, we add this layout's secondary extent to the accumulator. let size = layout.size.generalized(*sys); - extent.x = extent.x.max(size.x); - extent.y += size.y; + extent.width = extent.width.max(size.width); + extent.height += size.height; } // ------------------------------------------------------------------ // @@ -370,13 +369,11 @@ impl StackLayouter { // The space in which this layout is aligned is given by the // distances between the borders of its bounding box. - let usable = Size::new(bound.right - bound.left, bound.bottom - bound.top) - .generalized(sys); - + let usable = bound.size().generalized(sys); let local = usable.anchor(align, sys) - size.anchor(align, sys); - let pos = Size::new(bound.left, bound.top) + local.specialized(sys); + let pos = bound.origin() + local.to_size().specialized(sys).to_vec2(); - elements.extend_offset(pos, layout.elements); + elements.push_elements(pos, layout.elements); } self.layouts.push(BoxLayout { size, align: self.ctx.align, elements }); @@ -406,7 +403,7 @@ impl Space { size: Size::ZERO, usable, extra: Size::ZERO, - rulers: Value4::with_all(GenAlign::Start), + rulers: Sides::uniform(GenAlign::Start), last_spacing: LastSpacing::Hard, } } diff --git a/src/layout/tree.rs b/src/layout/tree.rs index b43ef089e..587742154 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -202,16 +202,13 @@ impl<'a> TreeLayouter<'a> { // The line layouter has no idea of page styles and thus we // need to recompute the layouting space resulting of the // new page style and update it within the layouter. - let margins = style.margins(); - self.ctx.base = style.size.unpadded(margins); - self.layouter.set_spaces( - vec![LayoutSpace { - size: style.size, - padding: margins, - expansion: LayoutExpansion::new(true, true), - }], - true, - ); + let space = LayoutSpace { + size: style.size, + insets: style.insets(), + expansion: LayoutExpansion::new(true, true), + }; + self.ctx.base = space.usable(); + self.layouter.set_spaces(vec![space], true); } else { error!( @self.feedback, span, diff --git a/src/lib.rs b/src/lib.rs index 8bf5398d4..3be978731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,17 +93,17 @@ impl Typesetter { /// Layout a syntax tree and return the produced layout. pub async fn layout(&self, tree: &SynTree) -> Pass { - let margins = self.style.page.margins(); + let space = LayoutSpace { + size: self.style.page.size, + insets: self.style.page.insets(), + expansion: LayoutExpansion::new(true, true), + }; layout(&tree, LayoutContext { loader: &self.loader, scope: &self.std, style: &self.style, - base: self.style.page.size.unpadded(margins), - spaces: vec![LayoutSpace { - size: self.style.page.size, - padding: margins, - expansion: LayoutExpansion::new(true, true), - }], + base: space.usable(), + spaces: vec![space], repeat: true, sys: LayoutSystem::new(Dir::LTR, Dir::TTB), align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 850252642..ac0bc19ec 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -4,8 +4,8 @@ use crate::length::ScaleLength; /// `box`: Layouts its contents into a box. /// /// # Keyword arguments -/// - `width`: The width of the box (length of relative to parent's width). -/// - `height`: The height of the box (length of relative to parent's height). +/// - `width`: The width of the box (length or relative to parent's width). +/// - `height`: The height of the box (length or relative to parent's height). pub async fn boxed( _: Span, mut args: DictValue, @@ -19,17 +19,17 @@ pub async fn boxed( ctx.spaces.truncate(1); ctx.repeat = false; - if let Some(w) = args.take_key::("width", &mut f) { - let length = w.raw_scaled(ctx.base.x); - ctx.base.x = length; - ctx.spaces[0].size.x = length; + if let Some(width) = args.take_key::("width", &mut f) { + let length = width.raw_scaled(ctx.base.width); + ctx.base.width = length; + ctx.spaces[0].size.width = length; ctx.spaces[0].expansion.horizontal = true; } - if let Some(h) = args.take_key::("height", &mut f) { - let length = h.raw_scaled(ctx.base.y); - ctx.base.y = length; - ctx.spaces[0].size.y = length; + if let Some(height) = args.take_key::("height", &mut f) { + let length = height.raw_scaled(ctx.base.height); + ctx.base.height = length; + ctx.spaces[0].size.height = length; ctx.spaces[0].expansion.vertical = true; } diff --git a/src/library/page.rs b/src/library/page.rs index b4f74e48a..8188bb672 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,4 +1,7 @@ +use std::mem; + use super::*; +use crate::geom::Sides; use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass}; @@ -27,36 +30,36 @@ pub async fn page(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass< if let Some(width) = args.take_key::("width", &mut f) { style.class = PaperClass::Custom; - style.size.x = width.as_raw(); + style.size.width = width.as_raw(); } if let Some(height) = args.take_key::("height", &mut f) { style.class = PaperClass::Custom; - style.size.y = height.as_raw(); + style.size.height = height.as_raw(); } if let Some(margins) = args.take_key::("margins", &mut f) { - style.margins.set_all(Some(margins)); + style.margins = Sides::uniform(Some(margins)); } if let Some(left) = args.take_key::("left", &mut f) { style.margins.left = Some(left); } - if let Some(right) = args.take_key::("right", &mut f) { - style.margins.right = Some(right); - } - if let Some(top) = args.take_key::("top", &mut f) { style.margins.top = Some(top); } + if let Some(right) = args.take_key::("right", &mut f) { + style.margins.right = Some(right); + } + if let Some(bottom) = args.take_key::("bottom", &mut f) { style.margins.bottom = Some(bottom); } if args.take_key::("flip", &mut f).unwrap_or(false) { - style.size.swap(); + mem::swap(&mut style.size.width, &mut style.size.height); } args.unexpected(&mut f); diff --git a/src/paper.rs b/src/paper.rs index 266f22d1b..8f855d5a1 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -1,6 +1,6 @@ //! Predefined papers. -use crate::geom::{Size, Value4}; +use crate::geom::{Sides, Size}; use crate::length::{Length, ScaleLength}; /// Specification of a paper. @@ -37,23 +37,16 @@ pub enum PaperClass { } impl PaperClass { - /// The default margins for this page class. - pub fn default_margins(self) -> Value4 { - let values = |l, t, r, b| { - Value4::new( - ScaleLength::Scaled(l), - ScaleLength::Scaled(t), - ScaleLength::Scaled(r), - ScaleLength::Scaled(b), - ) - }; - + /// The default margin ratios for this page class. + pub fn default_margins(self) -> Sides { + let s = ScaleLength::Scaled; + let f = |l, r, t, b| Sides::new(s(l), s(r), s(t), s(b)); match self { - Self::Custom => values(0.1190, 0.0842, 0.1190, 0.0842), - Self::Base => values(0.1190, 0.0842, 0.1190, 0.0842), - Self::US => values(0.1760, 0.1092, 0.1760, 0.0910), - Self::Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294), - Self::Book => values(0.1200, 0.0852, 0.1500, 0.0965), + Self::Custom => f(0.1190, 0.0842, 0.1190, 0.0842), + Self::Base => f(0.1190, 0.0842, 0.1190, 0.0842), + Self::US => f(0.1760, 0.1092, 0.1760, 0.0910), + Self::Newspaper => f(0.0455, 0.0587, 0.0455, 0.0294), + Self::Book => f(0.1200, 0.0852, 0.1500, 0.0965), } } } diff --git a/src/style.rs b/src/style.rs index 7da7f0c67..b6c1a2782 100644 --- a/src/style.rs +++ b/src/style.rs @@ -2,7 +2,7 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; -use crate::geom::{Margins, Size, Value4}; +use crate::geom::{Insets, Sides, Size}; use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass, PAPER_A4}; @@ -101,9 +101,9 @@ pub struct PageStyle { pub class: PaperClass, /// The width and height of the page. pub size: Size, - /// The amount of white space on each side. If a side is set to `None`, the - /// default for the paper class is used. - pub margins: Value4>, + /// The amount of white space in the order [left, top, right, bottom]. If a + /// side is set to `None`, the default for the paper class is used. + pub margins: Sides>, } impl PageStyle { @@ -112,19 +112,19 @@ impl PageStyle { Self { class: paper.class, size: paper.size(), - margins: Value4::with_all(None), + margins: Sides::uniform(None), } } - /// The absolute margins. - pub fn margins(&self) -> Margins { - let size = self.size; + /// The absolute insets. + pub fn insets(&self) -> Insets { + let Size { width, height } = self.size; let default = self.class.default_margins(); - Margins { - left: self.margins.left.unwrap_or(default.left).raw_scaled(size.x), - top: self.margins.top.unwrap_or(default.top).raw_scaled(size.y), - right: self.margins.right.unwrap_or(default.right).raw_scaled(size.x), - bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(size.y), + Insets { + x0: -self.margins.left.unwrap_or(default.left).raw_scaled(width), + y0: -self.margins.top.unwrap_or(default.top).raw_scaled(height), + x1: -self.margins.right.unwrap_or(default.right).raw_scaled(width), + y1: -self.margins.bottom.unwrap_or(default.bottom).raw_scaled(height), } } } diff --git a/tests/coma.typ b/tests/coma.typ index e1bf9f5b1..839335b70 100644 --- a/tests/coma.typ +++ b/tests/coma.typ @@ -7,10 +7,7 @@ Dr. Max Mustermann \ Ola Nordmann, John Doe ] -[align: right >> box][ - *WiSe 2019/2020* \ - Woche 3 -] +[align: right >> box][*WiSe 2019/2020* \ Woche 3] [v: 6mm] diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs index b81cecf6c..eb1b8ad09 100644 --- a/tests/test_typeset.rs +++ b/tests/test_typeset.rs @@ -13,7 +13,7 @@ use ttf_parser::OutlineBuilder; use typstc::export::pdf; use typstc::font::{FontLoader, SharedFontLoader}; -use typstc::geom::{Size, Value4}; +use typstc::geom::{Point, Sides, Size, Vec2}; use typstc::layout::elements::{LayoutElement, Shaped}; use typstc::layout::MultiLayout; use typstc::length::Length; @@ -66,10 +66,11 @@ fn main() { let loader = Rc::new(RefCell::new(loader)); let mut typesetter = Typesetter::new(loader.clone()); + let edge = Length::pt(250.0).as_raw(); typesetter.set_page_style(PageStyle { class: PaperClass::Custom, - size: Size::with_all(Length::pt(250.0).as_raw()), - margins: Value4::with_all(None), + size: Size::new(edge, edge), + margins: Sides::uniform(None), }); for (name, path, src) in filtered { @@ -156,24 +157,28 @@ fn render(layouts: &MultiLayout, loader: &FontLoader, scale: f64) -> DrawTarget let width = 2.0 * pad + layouts .iter() - .map(|l| scale * l.size.x) + .map(|l| scale * l.size.width) .max_by(|a, b| a.partial_cmp(&b).unwrap()) .unwrap() .round(); - let height = - pad + layouts.iter().map(|l| scale * l.size.y + pad).sum::().round(); + let height = pad + + layouts + .iter() + .map(|l| scale * l.size.height + pad) + .sum::() + .round(); let mut surface = DrawTarget::new(width as i32, height as i32); surface.clear(BLACK); - let mut offset = Size::new(pad, pad); + let mut offset = Vec2::new(pad, pad); for layout in layouts { surface.fill_rect( offset.x as f32, offset.y as f32, - (scale * layout.size.x) as f32, - (scale * layout.size.y) as f32, + (scale * layout.size.width) as f32, + (scale * layout.size.height) as f32, &Source::Solid(WHITE), &Default::default(), ); @@ -184,13 +189,13 @@ fn render(layouts: &MultiLayout, loader: &FontLoader, scale: f64) -> DrawTarget &mut surface, loader, shaped, - scale * pos + offset, + (scale * pos.to_vec2() + offset).to_point(), scale, ), } } - offset.y += scale * layout.size.y + pad; + offset.y += scale * layout.size.height + pad; } surface @@ -200,7 +205,7 @@ fn render_shaped( surface: &mut DrawTarget, loader: &FontLoader, shaped: &Shaped, - pos: Size, + pos: Point, scale: f64, ) { let face = loader.get_loaded(shaped.face).get();