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"]
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"

View File

@ -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);

View File

@ -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());
}

View File

@ -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<T> {
/// 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<T: Clone> Value2<T> {
/// 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<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 };
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<T> {
/// The left extent.
pub struct Sides<T> {
/// 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<T: Clone> Value4<T> {
impl<T> Sides<T> {
/// 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<T: Clone> Value4<T> {
/// 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<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 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));
}
}
}

View File

@ -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 {

View File

@ -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),
}
}

View File

@ -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)> {

View File

@ -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<GenAlign>,
rulers: Sides<GenAlign>,
/// 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,
}
}

View File

@ -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,

View File

@ -93,17 +93,17 @@ impl Typesetter {
/// Layout a syntax tree and return the produced layout.
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 {
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),

View File

@ -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::<ScaleLength>("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::<ScaleLength>("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::<ScaleLength>("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::<ScaleLength>("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;
}

View File

@ -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::<Length>("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::<Length>("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::<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) {
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) {
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) {
style.margins.bottom = Some(bottom);
}
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);

View File

@ -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<ScaleLength> {
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<ScaleLength> {
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),
}
}
}

View File

@ -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<Option<ScaleLength>>,
/// 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<Option<ScaleLength>>,
}
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),
}
}
}

View File

@ -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]

View File

@ -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::<f64>().round();
let height = pad
+ layouts
.iter()
.map(|l| scale * l.size.height + pad)
.sum::<f64>()
.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();