Port to kurbo 🎋

This commit is contained in:
Laurenz 2020-10-03 13:23:59 +02:00
parent 8dbc5b60cc
commit 0fc25d732d
17 changed files with 281 additions and 439 deletions

View File

@ -4,27 +4,31 @@ version = "0.1.0"
authors = ["The Typst Project Developers"] authors = ["The Typst Project Developers"]
edition = "2018" edition = "2018"
[lib]
bench = false
[workspace] [workspace]
members = ["main"] 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] [features]
default = ["serialize", "fs"] default = ["serialize", "fs"]
serialize = ["serde"] serialize = ["serde"]
fs = ["fontdock/fs"] 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] [dev-dependencies]
criterion = "0.3" criterion = "0.3"
futures-executor = "0.3" futures-executor = "0.3"

View File

@ -9,15 +9,10 @@ use typstc::parse::parse;
use typstc::Typesetter; use typstc::Typesetter;
const FONT_DIR: &str = "fonts"; const FONT_DIR: &str = "fonts";
// 28 not too dense lines.
const COMA: &str = include_str!("../tests/coma.typ"); const COMA: &str = include_str!("../tests/coma.typ");
fn parsing_benchmark(c: &mut Criterion) { fn parsing_benchmark(c: &mut Criterion) {
c.bench_function("parse-coma-28-lines", |b| b.iter(|| parse(COMA))); c.bench_function("parse-coma", |b| b.iter(|| parse(COMA)));
let long = COMA.repeat(100);
c.bench_function("parse-coma-2800-lines", |b| b.iter(|| parse(&long)));
} }
fn typesetting_benchmark(c: &mut Criterion) { 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 = FontLoader::new(Box::new(provider), descriptors);
let loader = Rc::new(RefCell::new(loader)); let loader = Rc::new(RefCell::new(loader));
let typesetter = Typesetter::new(loader.clone()); let typesetter = Typesetter::new(loader.clone());
c.bench_function("typeset-coma", |b| {
c.bench_function("typeset-coma-28-lines", |b| {
b.iter(|| futures_executor::block_on(typesetter.typeset(COMA))) 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); criterion_group!(benches, parsing_benchmark, typesetting_benchmark);

View File

@ -111,8 +111,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
let rect = Rect::new( let rect = Rect::new(
0.0, 0.0,
0.0, 0.0,
Length::raw(page.size.x).as_pt() as f32, Length::raw(page.size.width).as_pt() as f32,
Length::raw(page.size.y).as_pt() as f32, Length::raw(page.size.height).as_pt() as f32,
); );
self.writer.write_obj( self.writer.write_obj(
@ -152,7 +152,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
} }
let x = Length::raw(pos.x).as_pt(); 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.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
text.tj(shaped.encode_glyphs_be()); text.tj(shaped.encode_glyphs_be());
} }

View File

@ -1,172 +1,100 @@
//! Geometrical types. //! Geometrical types.
use std::fmt::{self, Debug, Formatter}; #[doc(no_inline)]
use std::ops::*; pub use kurbo::*;
use crate::layout::primitive::*; use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis};
/// A value in two dimensions. /// Additional methods for [sizes].
#[derive(Default, Copy, Clone, Eq, PartialEq)] ///
pub struct Value2<T> { /// [sizes]: ../../kurbo/struct.Size.html
/// The horizontal component. pub trait SizeExt {
pub x: T, /// Return the primary component of this specialized size.
/// The vertical component. fn primary(self, sys: LayoutSystem) -> f64;
pub y: T,
}
impl<T: Clone> Value2<T> { /// Borrow the primary component of this specialized size mutably.
/// Create a new 2D-value from two values. fn primary_mut(&mut self, sys: LayoutSystem) -> &mut f64;
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
/// Create a new 2D-value with `x` set to a value and `y` to default. /// Return the secondary component of this specialized size.
pub fn with_x(x: T) -> Self fn secondary(self, sys: LayoutSystem) -> f64;
where
T: Default,
{
Self { x, y: T::default() }
}
/// Create a new 2D-value with `y` set to a value and `x` to default. /// Borrow the secondary component of this specialized size mutably.
pub fn with_y(y: T) -> Self fn secondary_mut(&mut self, sys: LayoutSystem) -> &mut f64;
where
T: Default,
{
Self { x: T::default(), y }
}
/// Create a 2D-value with `x` and `y` set to the same value `s`. /// Returns the generalized version of a `Size` based on the layouting
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
/// system, that is: /// system, that is:
/// - `x` describes the primary axis instead of the horizontal one. /// - `x` describes the primary axis instead of the horizontal one.
/// - `y` describes the secondary axis instead of the vertical one. /// - `y` describes the secondary axis instead of the vertical one.
pub fn generalized(self, sys: LayoutSystem) -> Self { fn generalized(self, sys: LayoutSystem) -> Self;
match sys.primary.axis() {
SpecAxis::Horizontal => self,
SpecAxis::Vertical => Self { x: self.y, y: self.x },
}
}
/// Returns the specialized version of this generalized Size2D (inverse to /// Returns the specialized version of this generalized Size2D (inverse to
/// `generalized`). /// `generalized`).
pub fn specialized(self, sys: LayoutSystem) -> Self { 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<T: Debug> Debug for Value2<T> {
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<f64>;
impl Size {
/// The zeroed size.
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
/// Whether the given size fits into this one, that is, both coordinate /// Whether the given size fits into this one, that is, both coordinate
/// values are smaller or equal. /// values are smaller or equal.
pub fn fits(self, other: Self) -> bool { 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,
}
}
/// The anchor position along the given axis for an item with the given /// The anchor position along the given axis for an item with the given
/// alignment in a container with this size. /// alignment in a container with this size.
/// ///
/// This assumes the size to be generalized such that `x` corresponds to the /// This assumes the size to be generalized such that `x` corresponds to the
/// primary axis. /// primary axis.
pub fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Self { fn anchor(self, align: LayoutAlign, sys: LayoutSystem) -> Point;
Size {
x: anchor(self.x, align.primary, sys.primary),
y: anchor(self.y, align.secondary, sys.secondary),
} }
impl SizeExt for Size {
fn primary(self, sys: LayoutSystem) -> f64 {
if sys.primary.axis() == SpecAxis::Horizontal {
self.width
} else {
self.height
} }
} }
fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 { 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) { match (dir.is_positive(), align) {
(true, GenAlign::Start) | (false, GenAlign::End) => 0.0, (true, GenAlign::Start) | (false, GenAlign::End) => 0.0,
(_, GenAlign::Center) => length / 2.0, (_, GenAlign::Center) => length / 2.0,
@ -174,36 +102,60 @@ fn anchor(length: f64, align: GenAlign, dir: Dir) -> f64 {
} }
} }
impl Neg for Size { Point::new(
type Output = Size; length_anchor(self.width, align.primary, sys.primary),
length_anchor(self.height, align.secondary, sys.secondary),
fn neg(self) -> Size { )
Size { x: -self.x, y: -self.y }
} }
} }
/// A value in four dimensions. /// 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,
}
}
}
/// A generic container for `[left, top, right, bottom]` values.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
pub struct Value4<T> { pub struct Sides<T> {
/// The left extent. /// The value for the left side.
pub left: T, pub left: T,
/// The top extent. /// The value for the top side.
pub top: T, pub top: T,
/// The right extent. /// The value for the right side.
pub right: T, pub right: T,
/// The bottom extent. /// The value for the bottom side.
pub bottom: T, pub bottom: T,
} }
impl<T: Clone> Value4<T> { impl<T> Sides<T> {
/// Create a new box from four sizes. /// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> Self { 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`. /// Create an instance with all four components set to the same `value`.
pub fn with_all(value: T) -> Self { pub fn uniform(value: T) -> Self
Value4 { where
T: Clone,
{
Self {
left: value.clone(), left: value.clone(),
top: value.clone(), top: value.clone(),
right: value.clone(), right: value.clone(),
@ -215,105 +167,12 @@ impl<T: Clone> Value4<T> {
/// alignment. /// alignment.
/// ///
/// Center alignment is treated the same as origin alignment. /// Center alignment is treated the same as origin alignment.
pub fn get_mut(&mut self, mut dir: Dir, align: GenAlign) -> &mut T { pub fn get_mut(&mut self, dir: Dir, align: GenAlign) -> &mut T {
if align == GenAlign::End { match if align == GenAlign::End { dir.inv() } else { dir } {
dir = dir.inv();
}
match dir {
Dir::LTR => &mut self.left, Dir::LTR => &mut self.left,
Dir::RTL => &mut self.right, Dir::RTL => &mut self.right,
Dir::TTB => &mut self.top, Dir::TTB => &mut self.top,
Dir::BTT => &mut self.bottom, 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<f64>;
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] }

View File

@ -5,11 +5,11 @@ use std::fmt::{self, Debug, Formatter};
use fontdock::FaceId; use fontdock::FaceId;
use ttf_parser::GlyphId; use ttf_parser::GlyphId;
use crate::geom::Size; use crate::geom::Point;
/// A collection of absolutely positioned layout elements. /// A collection of absolutely positioned layout elements.
#[derive(Debug, Default, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct LayoutElements(pub Vec<(Size, LayoutElement)>); pub struct LayoutElements(pub Vec<(Point, LayoutElement)>);
impl LayoutElements { impl LayoutElements {
/// Create an new empty collection. /// Create an new empty collection.
@ -18,16 +18,15 @@ impl LayoutElements {
} }
/// Add an element at a position. /// 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)); self.0.push((pos, element));
} }
/// Add all elements of another collection, offsetting each by the given /// Add all elements of another collection, placing them relative to the
/// `offset`. This can be used to place a sublayout at a position in another /// given position.
/// layout. pub fn push_elements(&mut self, pos: Point, more: Self) {
pub fn extend_offset(&mut self, offset: Size, more: Self) {
for (subpos, element) in more.0 { for (subpos, element) in more.0 {
self.0.push((subpos + offset, element)); self.0.push((pos + subpos.to_vec2(), element));
} }
} }
} }

View File

@ -95,14 +95,14 @@ impl LineLayouter {
let usable = self.stack.usable().primary(sys); let usable = self.stack.usable().primary(sys);
rest_run.usable = Some(match layout.align.primary { rest_run.usable = Some(match layout.align.primary {
GenAlign::Start => unreachable!("start > x"), GenAlign::Start => unreachable!("start > x"),
GenAlign::Center => usable - 2.0 * self.run.size.x, GenAlign::Center => usable - 2.0 * self.run.size.width,
GenAlign::End => usable - self.run.size.x, 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.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; self.run = rest_run;
} }
@ -126,10 +126,10 @@ impl LineLayouter {
} }
self.run.align = Some(layout.align); 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.width += size.width;
self.run.size.y = self.run.size.y.max(size.y); self.run.size.height = self.run.size.height.max(size.height);
self.run.last_spacing = LastSpacing::None; self.run.last_spacing = LastSpacing::None;
} }
@ -152,10 +152,10 @@ impl LineLayouter {
// If there was another run already, override the stack's size. // If there was another run already, override the stack's size.
if let Some(primary) = self.run.usable { 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 usable
} }
@ -163,8 +163,8 @@ impl LineLayouter {
pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
match kind { match kind {
SpacingKind::Hard => { SpacingKind::Hard => {
spacing = spacing.min(self.usable().x); spacing = spacing.min(self.usable().width);
self.run.size.x += spacing; self.run.size.width += spacing;
self.run.last_spacing = LastSpacing::Hard; self.run.last_spacing = LastSpacing::Hard;
} }
@ -215,7 +215,7 @@ impl LineLayouter {
/// it will fit into this layouter's underlying stack. /// it will fit into this layouter's underlying stack.
pub fn remaining(&self) -> LayoutSpaces { pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = self.stack.remaining(); 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 spaces
} }
@ -246,11 +246,11 @@ impl LineLayouter {
for (offset, layout) in layouts { for (offset, layout) in layouts {
let x = match self.ctx.sys.primary.is_positive() { let x = match self.ctx.sys.primary.is_positive() {
true => offset, 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); let pos = Point::new(x, 0.0);
elements.extend_offset(pos, layout.elements); elements.push_elements(pos, layout.elements);
} }
self.stack.add(BoxLayout { self.stack.add(BoxLayout {

View File

@ -10,9 +10,10 @@ mod tree;
pub use primitive::*; pub use primitive::*;
pub use tree::layout_tree as layout; pub use tree::layout_tree as layout;
use crate::geom::{Insets, Point, Rect, RectExt, Sides, Size, SizeExt};
use crate::eval::Scope; use crate::eval::Scope;
use crate::font::SharedFontLoader; use crate::font::SharedFontLoader;
use crate::geom::{Margins, Size};
use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::SynTree; use crate::syntax::SynTree;
@ -67,22 +68,21 @@ pub struct LayoutSpace {
/// The maximum size of the rectangle to layout into. /// The maximum size of the rectangle to layout into.
pub size: Size, pub size: Size,
/// Padding that should be respected on each side. /// 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 /// Whether to expand the size of the resulting layout to the full size of
/// this space or to shrink it to fit the content. /// this space or to shrink it to fit the content.
pub expansion: LayoutExpansion, pub expansion: LayoutExpansion,
} }
impl LayoutSpace { impl LayoutSpace {
/// The offset from the origin to the start of content, i.e. /// The position of the padded start in the space.
/// `(padding.left, padding.top)`. pub fn start(&self) -> Point {
pub fn start(&self) -> Size { Point::new(-self.insets.x0, -self.insets.y0)
Size::new(self.padding.left, self.padding.top)
} }
/// The actually usable area (size minus padding). /// The actually usable area (size minus padding).
pub fn usable(&self) -> Size { 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 /// The inner layout space with size reduced by the padding, zero padding of
@ -90,7 +90,7 @@ impl LayoutSpace {
pub fn inner(&self) -> Self { pub fn inner(&self) -> Self {
Self { Self {
size: self.usable(), size: self.usable(),
padding: Margins::ZERO, insets: Insets::ZERO,
expansion: LayoutExpansion::new(false, false), expansion: LayoutExpansion::new(false, false),
} }
} }

View File

@ -11,7 +11,6 @@ use super::elements::{LayoutElement, Shaped};
use super::BoxLayout as Layout; use super::BoxLayout as Layout;
use super::*; use super::*;
use crate::font::FontLoader; use crate::font::FontLoader;
use crate::geom::Size;
use crate::style::TextStyle; use crate::style::TextStyle;
/// Shape text into a box. /// Shape text into a box.
@ -74,7 +73,7 @@ impl<'a> Shaper<'a> {
// Flush the last buffered parts of the word. // Flush the last buffered parts of the word.
if !self.shaped.text.is_empty() { 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)); 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()), 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.layout.elements.push(pos, LayoutElement::Text(shaped));
self.offset = self.layout.size.x; self.offset = self.layout.size.width;
} }
self.shaped.face = index; self.shaped.face = index;
@ -107,9 +106,9 @@ impl<'a> Shaper<'a> {
self.shaped.text.push(c); self.shaped.text.push(c);
self.shaped.glyphs.push(glyph); 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)> { async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {

View File

@ -20,7 +20,6 @@
//! sentence in the second box. //! sentence in the second box.
use super::*; use super::*;
use crate::geom::Value4;
/// Performs the stack layouting. /// Performs the stack layouting.
pub struct StackLayouter { pub struct StackLayouter {
@ -64,7 +63,7 @@ struct Space {
/// Dictate which alignments for new boxes are still allowed and which /// Dictate which alignments for new boxes are still allowed and which
/// require a new space to be started. For example, after an `End`-aligned /// require a new space to be started. For example, after an `End`-aligned
/// item, no `Start`-aligned one can follow. /// item, no `Start`-aligned one can follow.
rulers: Value4<GenAlign>, rulers: Sides<GenAlign>,
/// The spacing state. This influences how new spacing is handled, e.g. hard /// The spacing state. This influences how new spacing is handled, e.g. hard
/// spacing may override soft spacing. /// spacing may override soft spacing.
last_spacing: LastSpacing, last_spacing: LastSpacing,
@ -127,7 +126,7 @@ impl StackLayouter {
SpacingKind::Hard => { SpacingKind::Hard => {
// Reduce the spacing such that it definitely fits. // Reduce the spacing such that it definitely fits.
spacing = spacing.min(self.space.usable.secondary(self.ctx.sys)); 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.update_metrics(size);
self.space.layouts.push((self.ctx.sys, BoxLayout { self.space.layouts.push((self.ctx.sys, BoxLayout {
@ -161,15 +160,15 @@ impl StackLayouter {
let mut size = self.space.size.generalized(sys); let mut size = self.space.size.generalized(sys);
let mut extra = self.space.extra.generalized(sys); let mut extra = self.space.extra.generalized(sys);
size.x += (added.x - extra.x).max(0.0); size.width += (added.width - extra.width).max(0.0);
size.y += (added.y - extra.y).max(0.0); size.height += (added.height - extra.height).max(0.0);
extra.x = extra.x.max(added.x); extra.width = extra.width.max(added.width);
extra.y = (extra.y - added.y).max(0.0); extra.height = (extra.height - added.height).max(0.0);
self.space.size = size.specialized(sys); self.space.size = size.specialized(sys);
self.space.extra = extra.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. /// Returns true if a space break is necessary.
@ -239,7 +238,7 @@ impl StackLayouter {
let mut spaces = vec![LayoutSpace { let mut spaces = vec![LayoutSpace {
size, size,
padding: Margins::ZERO, insets: Insets::ZERO,
expansion: LayoutExpansion::new(false, false), expansion: LayoutExpansion::new(false, false),
}]; }];
@ -253,7 +252,7 @@ impl StackLayouter {
/// The remaining usable size. /// The remaining usable size.
pub fn usable(&self) -> Size { pub fn usable(&self) -> Size {
self.space.usable 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) .specialized(self.ctx.sys)
} }
@ -286,13 +285,13 @@ impl StackLayouter {
let usable = space.usable(); let usable = space.usable();
if space.expansion.horizontal { if space.expansion.horizontal {
self.space.size.x = usable.x; self.space.size.width = usable.width;
} }
if space.expansion.vertical { 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 // Step 2: Forward pass. Create a bounding box for each layout in which
@ -302,11 +301,11 @@ impl StackLayouter {
let start = space.start(); let start = space.start();
let mut bounds = vec![]; let mut bounds = vec![];
let mut bound = Margins { let mut bound = Rect {
left: start.x, x0: start.x,
top: start.y, y0: start.y,
right: start.x + self.space.size.x, x1: start.x + self.space.size.width,
bottom: start.y + self.space.size.y, y1: start.y + self.space.size.height,
}; };
for (sys, layout) in &self.space.layouts { 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 // is thus stored in `extent.y`. The primary extent is reset for
// this new axis-aligned run. // this new axis-aligned run.
if rotation != sys.secondary.axis() { if rotation != sys.secondary.axis() {
extent.y = extent.x; extent.height = extent.width;
extent.x = 0.0; extent.width = 0.0;
rotation = sys.secondary.axis(); rotation = sys.secondary.axis();
} }
@ -349,12 +348,12 @@ impl StackLayouter {
// accumulated secondary extent of all layouts we have seen so far, // accumulated secondary extent of all layouts we have seen so far,
// which are the layouts after this one since we iterate reversed. // which are the layouts after this one since we iterate reversed.
*bound.get_mut(sys.secondary, GenAlign::End) -= *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. // Then, we add this layout's secondary extent to the accumulator.
let size = layout.size.generalized(*sys); let size = layout.size.generalized(*sys);
extent.x = extent.x.max(size.x); extent.width = extent.width.max(size.width);
extent.y += size.y; extent.height += size.height;
} }
// ------------------------------------------------------------------ // // ------------------------------------------------------------------ //
@ -370,13 +369,11 @@ impl StackLayouter {
// The space in which this layout is aligned is given by the // The space in which this layout is aligned is given by the
// distances between the borders of its bounding box. // distances between the borders of its bounding box.
let usable = Size::new(bound.right - bound.left, bound.bottom - bound.top) let usable = bound.size().generalized(sys);
.generalized(sys);
let local = usable.anchor(align, sys) - size.anchor(align, 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 }); self.layouts.push(BoxLayout { size, align: self.ctx.align, elements });
@ -406,7 +403,7 @@ impl Space {
size: Size::ZERO, size: Size::ZERO,
usable, usable,
extra: Size::ZERO, extra: Size::ZERO,
rulers: Value4::with_all(GenAlign::Start), rulers: Sides::uniform(GenAlign::Start),
last_spacing: LastSpacing::Hard, last_spacing: LastSpacing::Hard,
} }
} }

View File

@ -202,16 +202,13 @@ impl<'a> TreeLayouter<'a> {
// The line layouter has no idea of page styles and thus we // The line layouter has no idea of page styles and thus we
// need to recompute the layouting space resulting of the // need to recompute the layouting space resulting of the
// new page style and update it within the layouter. // new page style and update it within the layouter.
let margins = style.margins(); let space = LayoutSpace {
self.ctx.base = style.size.unpadded(margins);
self.layouter.set_spaces(
vec![LayoutSpace {
size: style.size, size: style.size,
padding: margins, insets: style.insets(),
expansion: LayoutExpansion::new(true, true), expansion: LayoutExpansion::new(true, true),
}], };
true, self.ctx.base = space.usable();
); self.layouter.set_spaces(vec![space], true);
} else { } else {
error!( error!(
@self.feedback, span, @self.feedback, span,

View File

@ -93,17 +93,17 @@ impl Typesetter {
/// Layout a syntax tree and return the produced layout. /// Layout a syntax tree and return the produced layout.
pub async fn layout(&self, tree: &SynTree) -> Pass<MultiLayout> { pub async fn layout(&self, tree: &SynTree) -> Pass<MultiLayout> {
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 { layout(&tree, LayoutContext {
loader: &self.loader, loader: &self.loader,
scope: &self.std, scope: &self.std,
style: &self.style, style: &self.style,
base: self.style.page.size.unpadded(margins), base: space.usable(),
spaces: vec![LayoutSpace { spaces: vec![space],
size: self.style.page.size,
padding: margins,
expansion: LayoutExpansion::new(true, true),
}],
repeat: true, repeat: true,
sys: LayoutSystem::new(Dir::LTR, Dir::TTB), sys: LayoutSystem::new(Dir::LTR, Dir::TTB),
align: LayoutAlign::new(GenAlign::Start, GenAlign::Start), align: LayoutAlign::new(GenAlign::Start, GenAlign::Start),

View File

@ -4,8 +4,8 @@ use crate::length::ScaleLength;
/// `box`: Layouts its contents into a box. /// `box`: Layouts its contents into a box.
/// ///
/// # Keyword arguments /// # Keyword arguments
/// - `width`: The width of the box (length of relative to parent's width). /// - `width`: The width of the box (length or relative to parent's width).
/// - `height`: The height of the box (length of relative to parent's height). /// - `height`: The height of the box (length or relative to parent's height).
pub async fn boxed( pub async fn boxed(
_: Span, _: Span,
mut args: DictValue, mut args: DictValue,
@ -19,17 +19,17 @@ pub async fn boxed(
ctx.spaces.truncate(1); ctx.spaces.truncate(1);
ctx.repeat = false; ctx.repeat = false;
if let Some(w) = args.take_key::<ScaleLength>("width", &mut f) { if let Some(width) = args.take_key::<ScaleLength>("width", &mut f) {
let length = w.raw_scaled(ctx.base.x); let length = width.raw_scaled(ctx.base.width);
ctx.base.x = length; ctx.base.width = length;
ctx.spaces[0].size.x = length; ctx.spaces[0].size.width = length;
ctx.spaces[0].expansion.horizontal = true; ctx.spaces[0].expansion.horizontal = true;
} }
if let Some(h) = args.take_key::<ScaleLength>("height", &mut f) { if let Some(height) = args.take_key::<ScaleLength>("height", &mut f) {
let length = h.raw_scaled(ctx.base.y); let length = height.raw_scaled(ctx.base.height);
ctx.base.y = length; ctx.base.height = length;
ctx.spaces[0].size.y = length; ctx.spaces[0].size.height = length;
ctx.spaces[0].expansion.vertical = true; ctx.spaces[0].expansion.vertical = true;
} }

View File

@ -1,4 +1,7 @@
use std::mem;
use super::*; use super::*;
use crate::geom::Sides;
use crate::length::{Length, ScaleLength}; use crate::length::{Length, ScaleLength};
use crate::paper::{Paper, PaperClass}; 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::<Length>("width", &mut f) { if let Some(width) = args.take_key::<Length>("width", &mut f) {
style.class = PaperClass::Custom; style.class = PaperClass::Custom;
style.size.x = width.as_raw(); style.size.width = width.as_raw();
} }
if let Some(height) = args.take_key::<Length>("height", &mut f) { if let Some(height) = args.take_key::<Length>("height", &mut f) {
style.class = PaperClass::Custom; style.class = PaperClass::Custom;
style.size.y = height.as_raw(); style.size.height = height.as_raw();
} }
if let Some(margins) = args.take_key::<ScaleLength>("margins", &mut f) { if let Some(margins) = args.take_key::<ScaleLength>("margins", &mut f) {
style.margins.set_all(Some(margins)); style.margins = Sides::uniform(Some(margins));
} }
if let Some(left) = args.take_key::<ScaleLength>("left", &mut f) { if let Some(left) = args.take_key::<ScaleLength>("left", &mut f) {
style.margins.left = Some(left); style.margins.left = Some(left);
} }
if let Some(right) = args.take_key::<ScaleLength>("right", &mut f) {
style.margins.right = Some(right);
}
if let Some(top) = args.take_key::<ScaleLength>("top", &mut f) { if let Some(top) = args.take_key::<ScaleLength>("top", &mut f) {
style.margins.top = Some(top); style.margins.top = Some(top);
} }
if let Some(right) = args.take_key::<ScaleLength>("right", &mut f) {
style.margins.right = Some(right);
}
if let Some(bottom) = args.take_key::<ScaleLength>("bottom", &mut f) { if let Some(bottom) = args.take_key::<ScaleLength>("bottom", &mut f) {
style.margins.bottom = Some(bottom); style.margins.bottom = Some(bottom);
} }
if args.take_key::<bool>("flip", &mut f).unwrap_or(false) { if args.take_key::<bool>("flip", &mut f).unwrap_or(false) {
style.size.swap(); mem::swap(&mut style.size.width, &mut style.size.height);
} }
args.unexpected(&mut f); args.unexpected(&mut f);

View File

@ -1,6 +1,6 @@
//! Predefined papers. //! Predefined papers.
use crate::geom::{Size, Value4}; use crate::geom::{Sides, Size};
use crate::length::{Length, ScaleLength}; use crate::length::{Length, ScaleLength};
/// Specification of a paper. /// Specification of a paper.
@ -37,23 +37,16 @@ pub enum PaperClass {
} }
impl PaperClass { impl PaperClass {
/// The default margins for this page class. /// The default margin ratios for this page class.
pub fn default_margins(self) -> Value4<ScaleLength> { pub fn default_margins(self) -> Sides<ScaleLength> {
let values = |l, t, r, b| { let s = ScaleLength::Scaled;
Value4::new( let f = |l, r, t, b| Sides::new(s(l), s(r), s(t), s(b));
ScaleLength::Scaled(l),
ScaleLength::Scaled(t),
ScaleLength::Scaled(r),
ScaleLength::Scaled(b),
)
};
match self { match self {
Self::Custom => values(0.1190, 0.0842, 0.1190, 0.0842), Self::Custom => f(0.1190, 0.0842, 0.1190, 0.0842),
Self::Base => values(0.1190, 0.0842, 0.1190, 0.0842), Self::Base => f(0.1190, 0.0842, 0.1190, 0.0842),
Self::US => values(0.1760, 0.1092, 0.1760, 0.0910), Self::US => f(0.1760, 0.1092, 0.1760, 0.0910),
Self::Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294), Self::Newspaper => f(0.0455, 0.0587, 0.0455, 0.0294),
Self::Book => values(0.1200, 0.0852, 0.1500, 0.0965), Self::Book => f(0.1200, 0.0852, 0.1500, 0.0965),
} }
} }
} }

View File

@ -2,7 +2,7 @@
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight}; 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::length::{Length, ScaleLength};
use crate::paper::{Paper, PaperClass, PAPER_A4}; use crate::paper::{Paper, PaperClass, PAPER_A4};
@ -101,9 +101,9 @@ pub struct PageStyle {
pub class: PaperClass, pub class: PaperClass,
/// The width and height of the page. /// The width and height of the page.
pub size: Size, pub size: Size,
/// The amount of white space on each side. If a side is set to `None`, the /// The amount of white space in the order [left, top, right, bottom]. If a
/// default for the paper class is used. /// side is set to `None`, the default for the paper class is used.
pub margins: Value4<Option<ScaleLength>>, pub margins: Sides<Option<ScaleLength>>,
} }
impl PageStyle { impl PageStyle {
@ -112,19 +112,19 @@ impl PageStyle {
Self { Self {
class: paper.class, class: paper.class,
size: paper.size(), size: paper.size(),
margins: Value4::with_all(None), margins: Sides::uniform(None),
} }
} }
/// The absolute margins. /// The absolute insets.
pub fn margins(&self) -> Margins { pub fn insets(&self) -> Insets {
let size = self.size; let Size { width, height } = self.size;
let default = self.class.default_margins(); let default = self.class.default_margins();
Margins { Insets {
left: self.margins.left.unwrap_or(default.left).raw_scaled(size.x), x0: -self.margins.left.unwrap_or(default.left).raw_scaled(width),
top: self.margins.top.unwrap_or(default.top).raw_scaled(size.y), y0: -self.margins.top.unwrap_or(default.top).raw_scaled(height),
right: self.margins.right.unwrap_or(default.right).raw_scaled(size.x), x1: -self.margins.right.unwrap_or(default.right).raw_scaled(width),
bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(size.y), y1: -self.margins.bottom.unwrap_or(default.bottom).raw_scaled(height),
} }
} }
} }

View File

@ -7,10 +7,7 @@
Dr. Max Mustermann \ Dr. Max Mustermann \
Ola Nordmann, John Doe Ola Nordmann, John Doe
] ]
[align: right >> box][ [align: right >> box][*WiSe 2019/2020* \ Woche 3]
*WiSe 2019/2020* \
Woche 3
]
[v: 6mm] [v: 6mm]

View File

@ -13,7 +13,7 @@ use ttf_parser::OutlineBuilder;
use typstc::export::pdf; use typstc::export::pdf;
use typstc::font::{FontLoader, SharedFontLoader}; 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::elements::{LayoutElement, Shaped};
use typstc::layout::MultiLayout; use typstc::layout::MultiLayout;
use typstc::length::Length; use typstc::length::Length;
@ -66,10 +66,11 @@ fn main() {
let loader = Rc::new(RefCell::new(loader)); let loader = Rc::new(RefCell::new(loader));
let mut typesetter = Typesetter::new(loader.clone()); let mut typesetter = Typesetter::new(loader.clone());
let edge = Length::pt(250.0).as_raw();
typesetter.set_page_style(PageStyle { typesetter.set_page_style(PageStyle {
class: PaperClass::Custom, class: PaperClass::Custom,
size: Size::with_all(Length::pt(250.0).as_raw()), size: Size::new(edge, edge),
margins: Value4::with_all(None), margins: Sides::uniform(None),
}); });
for (name, path, src) in filtered { for (name, path, src) in filtered {
@ -156,24 +157,28 @@ fn render(layouts: &MultiLayout, loader: &FontLoader, scale: f64) -> DrawTarget
let width = 2.0 * pad let width = 2.0 * pad
+ layouts + layouts
.iter() .iter()
.map(|l| scale * l.size.x) .map(|l| scale * l.size.width)
.max_by(|a, b| a.partial_cmp(&b).unwrap()) .max_by(|a, b| a.partial_cmp(&b).unwrap())
.unwrap() .unwrap()
.round(); .round();
let height = let height = pad
pad + layouts.iter().map(|l| scale * l.size.y + pad).sum::<f64>().round(); + layouts
.iter()
.map(|l| scale * l.size.height + pad)
.sum::<f64>()
.round();
let mut surface = DrawTarget::new(width as i32, height as i32); let mut surface = DrawTarget::new(width as i32, height as i32);
surface.clear(BLACK); surface.clear(BLACK);
let mut offset = Size::new(pad, pad); let mut offset = Vec2::new(pad, pad);
for layout in layouts { for layout in layouts {
surface.fill_rect( surface.fill_rect(
offset.x as f32, offset.x as f32,
offset.y as f32, offset.y as f32,
(scale * layout.size.x) as f32, (scale * layout.size.width) as f32,
(scale * layout.size.y) as f32, (scale * layout.size.height) as f32,
&Source::Solid(WHITE), &Source::Solid(WHITE),
&Default::default(), &Default::default(),
); );
@ -184,13 +189,13 @@ fn render(layouts: &MultiLayout, loader: &FontLoader, scale: f64) -> DrawTarget
&mut surface, &mut surface,
loader, loader,
shaped, shaped,
scale * pos + offset, (scale * pos.to_vec2() + offset).to_point(),
scale, scale,
), ),
} }
} }
offset.y += scale * layout.size.y + pad; offset.y += scale * layout.size.height + pad;
} }
surface surface
@ -200,7 +205,7 @@ fn render_shaped(
surface: &mut DrawTarget, surface: &mut DrawTarget,
loader: &FontLoader, loader: &FontLoader,
shaped: &Shaped, shaped: &Shaped,
pos: Size, pos: Point,
scale: f64, scale: f64,
) { ) {
let face = loader.get_loaded(shaped.face).get(); let face = loader.get_loaded(shaped.face).get();