diff --git a/src/export/pdf.rs b/src/export/pdf.rs index e771617a2..f06e06af7 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -116,8 +116,8 @@ impl<'a, W: Write> PdfExporter<'a, W> { let rect = Rect::new( 0.0, 0.0, - page.dimensions.x.to_pt() as f32, - page.dimensions.y.to_pt() as f32, + Length::raw(page.dimensions.x).as_pt() as f32, + Length::raw(page.dimensions.y).as_pt() as f32, ); self.writer.write_obj( @@ -145,7 +145,7 @@ impl<'a, W: Write> PdfExporter<'a, W> { // needed. let mut text = Text::new(); let mut face_id = FaceId::MAX; - let mut font_size = Length::ZERO; + let mut font_size = 0.0; let mut next_pos = None; for action in &page.actions { @@ -157,13 +157,16 @@ impl<'a, W: Write> PdfExporter<'a, W> { &LayoutAction::SetFont(id, size) => { face_id = id; font_size = size; - text.tf(self.to_pdf[&id] as u32 + 1, font_size.to_pt() as f32); + text.tf( + self.to_pdf[&id] as u32 + 1, + Length::raw(font_size).as_pt() as f32 + ); } LayoutAction::WriteText(string) => { if let Some(pos) = next_pos.take() { - let x = pos.x.to_pt(); - let y = (page.dimensions.y - pos.y - font_size).to_pt(); + let x = Length::raw(pos.x).as_pt(); + let y = Length::raw(page.dimensions.y - pos.y - font_size).as_pt(); text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); } @@ -202,12 +205,10 @@ impl<'a, W: Write> PdfExporter<'a, W> { let base_font = format!("ABCDEF+{}", name); let system_info = CIDSystemInfo::new("Adobe", "Identity", 0); - let units_per_em = face.units_per_em().unwrap_or(1000); - let ratio = 1.0 / (units_per_em as f64); - let to_length = |x| Length::pt(ratio * x as f64); - let to_glyph_unit = |font_unit| { - let length = to_length(font_unit); - (1000.0 * length.to_pt()).round() as GlyphUnit + let units_per_em = face.units_per_em().unwrap_or(1000) as f64; + let ratio = 1.0 / units_per_em; + let to_glyph_unit = |font_unit: f64| { + (1000.0 * ratio * font_unit).round() as GlyphUnit }; let global_bbox = face.global_bounding_box(); diff --git a/src/geom.rs b/src/geom.rs new file mode 100644 index 000000000..dcac20004 --- /dev/null +++ b/src/geom.rs @@ -0,0 +1,324 @@ +//! Geometrical types. + +use std::fmt::{self, Debug, Formatter}; +use std::ops::*; + +use serde::Serialize; +use crate::layout::prelude::*; + +/// A value in two dimensions. +#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)] +pub struct Value2 { + /// The horizontal component. + pub x: T, + /// The vertical component. + pub y: T, +} + +impl Value2 { + /// Create a new 2D-value from two values. + pub fn new(x: T, y: T) -> Value2 { Value2 { x, y } } + + /// Create a new 2D-value with `x` set to a value and `y` to default. + pub fn with_x(x: T) -> Value2 where T: Default { + Value2 { x, y: T::default() } + } + + /// Create a new 2D-value with `y` set to a value and `x` to default. + pub fn with_y(y: T) -> Value2 where T: Default { + Value2 { x: T::default(), y } + } + + /// Create a new 2D-value with the primary axis set to a value and the other + /// one to default. + pub fn with_primary(v: T, axes: LayoutAxes) -> Value2 where T: Default { + Value2::with_x(v).generalized(axes) + } + + /// Create a new 2D-value with the secondary axis set to a value and the + /// other one to default. + pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2 where T: Default { + Value2::with_y(v).generalized(axes) + } + + /// Create a 2D-value with `x` and `y` set to the same value `s`. + pub fn with_all(s: T) -> Value2 { Value2 { x: s.clone(), y: s } } + + /// Get the specificed component. + pub fn get(self, axis: SpecificAxis) -> T { + match axis { + Horizontal => self.x, + Vertical => self.y, + } + } + + /// Borrow the specificed component mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T { + match axis { + Horizontal => &mut self.x, + Vertical => &mut self.y, + } + } + + /// Return the primary value of this specialized 2D-value. + pub fn primary(self, axes: LayoutAxes) -> T { + if axes.primary.axis() == Horizontal { self.x } else { self.y } + } + + /// Borrow the primary value of this specialized 2D-value mutably. + pub fn primary_mut(&mut self, axes: LayoutAxes) -> &mut T { + if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y } + } + + /// Return the secondary value of this specialized 2D-value. + pub fn secondary(self, axes: LayoutAxes) -> T { + if axes.primary.axis() == Horizontal { self.y } else { self.x } + } + + /// Borrow the secondary value of this specialized 2D-value mutably. + pub fn secondary_mut(&mut self, axes: LayoutAxes) -> &mut T { + if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x } + } + + /// Returns the generalized version of a `Size2D` dependent on the layouting + /// axes, 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, axes: LayoutAxes) -> Value2 { + match axes.primary.axis() { + Horizontal => self, + Vertical => Value2 { x: self.y, y: self.x }, + } + } + + /// Returns the specialized version of this generalized Size2D (inverse to + /// `generalized`). + pub fn specialized(self, axes: LayoutAxes) -> Value2 { + // In fact, generalized is its own inverse. For reasons of clarity + // at the call site, we still have this second function. + self.generalized(axes) + } + + /// Swap the `x` and `y` values. + pub fn swap(&mut self) { + std::mem::swap(&mut self.x, &mut self.y); + } +} + +impl Debug for Value2 where T: Debug { + 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: Size = Size { x: 0.0, y: 0.0 }; + + /// Whether the given size fits into this one, that is, both coordinate + /// values are smaller or equal. + pub fn fits(self, other: Size) -> 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) -> Size { + 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) -> Size { + 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 + /// 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, alignment: LayoutAlignment, axes: LayoutAxes) -> Size { + Size { + x: anchor(self.x, alignment.primary, axes.primary), + y: anchor(self.x, alignment.secondary, axes.secondary), + } + } +} + +fn anchor(length: f64, alignment: Alignment, direction: Direction) -> f64 { + match (direction.is_positive(), alignment) { + (true, Origin) | (false, End) => 0.0, + (_, Center) => length / 2.0, + (true, End) | (false, Origin) => length, + } +} + +impl Neg for Size { + type Output = Size; + + fn neg(self) -> Size { + Size { + x: -self.x, + y: -self.y, + } + } +} + +/// A value in four dimensions. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)] +pub struct Value4 { + /// The left extent. + pub left: T, + /// The top extent. + pub top: T, + /// The right extent. + pub right: T, + /// The bottom extent. + pub bottom: T, +} + +impl Value4 { + /// Create a new box from four sizes. + pub fn new(left: T, top: T, right: T, bottom: T) -> Value4 { + Value4 { left, top, right, bottom } + } + + /// Create a box with all four fields set to the same value `s`. + pub fn with_all(value: T) -> Value4 { + Value4 { + left: value.clone(), + top: value.clone(), + right: value.clone(), + bottom: value + } + } + + /// Get a mutable reference to the value for the specified direction at the + /// alignment. + /// + /// Center alignment is treated the same as origin alignment. + pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T { + if alignment == End { + direction = direction.inv(); + } + + match direction { + LeftToRight => &mut self.left, + RightToLeft => &mut self.right, + TopToBottom => &mut self.top, + BottomToTop => &mut self.bottom, + } + } + + /// Set all values to the given value. + pub fn set_all(&mut self, value: T) { + *self = Value4::with_all(value); + } + + /// Set the `left` and `right` values. + pub fn set_horizontal(&mut self, value: T) { + self.left = value.clone(); + self.right = value; + } + + /// Set the `top` and `bottom` values. + pub fn set_vertical(&mut self, value: T) { + self.top = value.clone(); + self.bottom = 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/actions.rs b/src/layout/actions.rs index 7806932ec..89c102853 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter}; use serde::ser::{Serialize, Serializer, SerializeTuple}; use fontdock::FaceId; -use crate::length::{Length, Size}; +use crate::geom::Size; use super::Layout; use self::LayoutAction::*; @@ -15,7 +15,7 @@ pub enum LayoutAction { /// Move to an absolute position. MoveAbsolute(Size), /// Set the font given the index from the font loader and font size. - SetFont(FaceId, Length), + SetFont(FaceId, f64), /// Write text at the current position. WriteText(String), /// Visualize a box for debugging purposes. @@ -82,9 +82,9 @@ impl Debug for LayoutAction { pub struct LayoutActions { origin: Size, actions: Vec, - active_font: (FaceId, Length), + active_font: (FaceId, f64), next_pos: Option, - next_font: Option<(FaceId, Length)>, + next_font: Option<(FaceId, f64)>, } impl LayoutActions { @@ -93,7 +93,7 @@ impl LayoutActions { LayoutActions { actions: vec![], origin: Size::ZERO, - active_font: (FaceId::MAX, Length::ZERO), + active_font: (FaceId::MAX, 0.0), next_pos: None, next_font: None, } diff --git a/src/layout/line.rs b/src/layout/line.rs index 1bb362042..0ef58878b 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -39,7 +39,7 @@ pub struct LineContext { /// extent of the layout. pub debug: bool, /// The line spacing. - pub line_spacing: Length, + pub line_spacing: f64, } /// A line run is a sequence of boxes with the same alignment that are arranged @@ -48,9 +48,8 @@ pub struct LineContext { #[derive(Debug)] struct LineRun { /// The so-far accumulated layouts in the line. - layouts: Vec<(Length, Layout)>, - /// The width (primary length) and maximal height (secondary length) of the - /// line. + layouts: Vec<(f64, Layout)>, + /// The width and maximal height of the line. size: Size, /// The alignment of all layouts in the line. /// @@ -60,7 +59,7 @@ struct LineRun { alignment: Option, /// If another line run with different alignment already took up some space /// of the line, this run has less space and how much is stored here. - usable: Option, + usable: Option, /// A possibly cached soft spacing or spacing state. last_spacing: LastSpacing, } @@ -104,7 +103,7 @@ impl LineLayouter { let usable = self.stack.usable().primary(axes); rest_run.usable = Some(match layout.alignment.primary { Alignment::Origin => unreachable!("origin > x"), - Alignment::Center => usable - 2 * self.run.size.x, + Alignment::Center => usable - 2.0 * self.run.size.x, Alignment::End => usable - self.run.size.x, }); @@ -138,7 +137,7 @@ impl LineLayouter { self.run.layouts.push((self.run.size.x, layout)); self.run.size.x += size.x; - self.run.size.y.max_eq(size.y); + self.run.size.y = self.run.size.y.max(size.y); self.run.last_spacing = LastSpacing::None; } @@ -170,11 +169,11 @@ impl LineLayouter { } /// Add spacing along the primary axis to the line. - pub fn add_primary_spacing(&mut self, mut spacing: Length, kind: SpacingKind) { + pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { // A hard space is simply an empty box. SpacingKind::Hard => { - spacing.min_eq(self.usable().x); + spacing = spacing.min(self.usable().x); self.run.size.x += spacing; self.run.last_spacing = LastSpacing::Hard; } @@ -196,7 +195,7 @@ impl LineLayouter { } /// Finish the line and add secondary spacing to the underlying stack. - pub fn add_secondary_spacing(&mut self, spacing: Length, kind: SpacingKind) { + pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) { self.finish_line_if_not_empty(); self.stack.add_spacing(spacing, kind) } @@ -218,7 +217,7 @@ impl LineLayouter { } /// Update the line spacing. - pub fn set_line_spacing(&mut self, line_spacing: Length) { + pub fn set_line_spacing(&mut self, line_spacing: f64) { self.ctx.line_spacing = line_spacing; } diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 8bcceda60..a6af0f827 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -5,7 +5,7 @@ use smallvec::SmallVec; use serde::Serialize; use fontdock::FaceId; -use crate::length::{Length, Size, Margins}; +use crate::geom::{Size, Margins}; use self::prelude::*; pub mod line; @@ -219,8 +219,8 @@ impl Direction { /// /// - `1` if the direction is positive. /// - `-1` if the direction is negative. - pub fn factor(self) -> i32 { - if self.is_positive() { 1 } else { -1 } + pub fn factor(self) -> f64 { + if self.is_positive() { 1.0 } else { -1.0 } } /// The inverse axis. @@ -368,17 +368,17 @@ enum LastSpacing { /// The last item was hard spacing. Hard, /// The last item was soft spacing with the given width and level. - Soft(Length, u32), + Soft(f64, u32), /// The last item was not spacing. None, } impl LastSpacing { - /// The length of the soft space if this is a soft space or zero otherwise. - fn soft_or_zero(self) -> Length { + /// The width of the soft space if this is a soft space or zero otherwise. + fn soft_or_zero(self) -> f64 { match self { LastSpacing::Soft(space, _) => space, - _ => Length::ZERO, + _ => 0.0, } } } diff --git a/src/layout/model.rs b/src/layout/model.rs index 3fb594d5a..c78c733eb 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -9,7 +9,7 @@ use smallvec::smallvec; use crate::{Pass, Feedback}; use crate::SharedFontLoader; use crate::style::{LayoutStyle, PageStyle, TextStyle}; -use crate::length::{Length, Size}; +use crate::geom::Size; use crate::syntax::{Model, SyntaxModel, Node, Decoration}; use crate::syntax::span::{Span, Spanned}; use super::line::{LineLayouter, LineContext}; @@ -74,7 +74,7 @@ pub enum Command<'a> { /// Add spacing of given [kind](super::SpacingKind) along the primary or /// secondary axis. The spacing kind defines how the spacing interacts with /// surrounding spacing. - AddSpacing(Length, SpacingKind, GenericAxis), + AddSpacing(f64, SpacingKind, GenericAxis), /// Start a new line. BreakLine, diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 20d99fa62..2dd67ea99 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -22,7 +22,7 @@ //! sentence in the second box. use smallvec::smallvec; -use crate::length::Value4; +use crate::geom::Value4; use super::*; /// Performs the stack layouting. @@ -128,12 +128,12 @@ impl StackLayouter { } /// Add secondary spacing to the stack. - pub fn add_spacing(&mut self, mut spacing: Length, kind: SpacingKind) { + pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { // A hard space is simply an empty box. SpacingKind::Hard => { // Reduce the spacing such that it definitely fits. - spacing.min_eq(self.space.usable.secondary(self.ctx.axes)); + spacing = spacing.min(self.space.usable.secondary(self.ctx.axes)); let dimensions = Size::with_y(spacing); self.update_metrics(dimensions); @@ -170,11 +170,11 @@ impl StackLayouter { let mut size = self.space.size.generalized(axes); let mut extra = self.space.extra.generalized(axes); - size.x += (dimensions.x - extra.x).max(Length::ZERO); - size.y += (dimensions.y - extra.y).max(Length::ZERO); + size.x += (dimensions.x - extra.x).max(0.0); + size.y += (dimensions.y - extra.y).max(0.0); - extra.x.max_eq(dimensions.x); - extra.y = (extra.y - dimensions.y).max(Length::ZERO); + extra.x = extra.x.max(dimensions.x); + extra.y = (extra.y - dimensions.y).max(0.0); self.space.size = size.specialized(axes); self.space.extra = extra.specialized(axes); @@ -348,7 +348,7 @@ impl StackLayouter { // is reset for this new axis-aligned run. if rotation != axes.secondary.axis() { extent.y = extent.x; - extent.x = Length::ZERO; + extent.x = 0.0; rotation = axes.secondary.axis(); } @@ -360,7 +360,7 @@ impl StackLayouter { // Then, we add this layout's secondary extent to the accumulator. let size = layout.dimensions.generalized(*axes); - extent.x.max_eq(size.x); + extent.x = extent.x.max(size.x); extent.y += size.y; } diff --git a/src/layout/text.rs b/src/layout/text.rs index 226166679..30995be05 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -6,7 +6,7 @@ use fontdock::{FaceId, FaceQuery, FontStyle}; use crate::font::SharedFontLoader; -use crate::length::{Length, Size}; +use crate::geom::Size; use crate::style::TextStyle; use super::*; @@ -18,7 +18,7 @@ struct TextLayouter<'a> { actions: LayoutActions, buffer: String, active_font: FaceId, - width: Length, + width: f64, } /// The context for text layouting. @@ -51,7 +51,7 @@ impl<'a> TextLayouter<'a> { actions: LayoutActions::new(), buffer: String::new(), active_font: FaceId::MAX, - width: Length::ZERO, + width: 0.0, } } @@ -107,7 +107,7 @@ impl<'a> TextLayouter<'a> { /// Select the best font for a character and return its index along with /// the width of the char in the font. - async fn select_font(&mut self, c: char) -> Option<(FaceId, Length)> { + async fn select_font(&mut self, c: char) -> Option<(FaceId, f64)> { let mut loader = self.ctx.loader.borrow_mut(); let mut variant = self.ctx.style.variant; @@ -132,14 +132,13 @@ impl<'a> TextLayouter<'a> { if let Some((id, face)) = loader.query(query).await { // Determine the width of the char. - let units_per_em = face.units_per_em().unwrap_or(1000); - let ratio = 1.0 / (units_per_em as f64); - let to_length = |x| Length::pt(ratio * x as f64); + let units_per_em = face.units_per_em().unwrap_or(1000) as f64; + let ratio = 1.0 / units_per_em; + let to_raw = |x| ratio * x as f64; let glyph = face.glyph_index(c)?; let glyph_width = face.glyph_hor_advance(glyph)?; - let char_width = to_length(glyph_width) - * self.ctx.style.font_size().to_pt(); + let char_width = to_raw(glyph_width) * self.ctx.style.font_size(); Some((id, char_width)) } else { diff --git a/src/length.rs b/src/length.rs index 8131a6e9c..4c14c894b 100644 --- a/src/length.rs +++ b/src/length.rs @@ -1,79 +1,113 @@ -//! Different-dimensional value and spacing types. +//! A length type with a unit. use std::fmt::{self, Debug, Display, Formatter}; -use std::iter::Sum; -use std::ops::*; use std::str::FromStr; -use serde::Serialize; -use crate::layout::prelude::*; - -/// A general spacing type. -#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)] -#[serde(transparent)] +/// A length with a unit. +#[derive(Copy, Clone, PartialEq)] pub struct Length { - /// The length in typographic points (1/72 inches). - pub points: f64, + /// The length in the given unit. + pub val: f64, + /// The unit of measurement. + pub unit: Unit, +} + +/// Different units of measurement. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Unit { + /// Points. + Pt, + /// Millimeters. + Mm, + /// Centimeters. + Cm, + /// Inches. + In, + /// Raw units (the implicit unit of all bare `f64` lengths). + Raw, } impl Length { - /// The zeroed length. - pub const ZERO: Length = Length { points: 0.0 }; - - /// Create a length from an amount of points. - pub fn pt(points: f64) -> Length { Length { points } } - - /// Create a length from an amount of millimeters. - pub fn mm(mm: f64) -> Length { Length { points: 2.83465 * mm } } - - /// Create a length from an amount of centimeters. - pub fn cm(cm: f64) -> Length { Length { points: 28.3465 * cm } } - - /// Create a length from an amount of inches. - pub fn inches(inches: f64) -> Length { Length { points: 72.0 * inches } } - - /// Convert this length into points. - pub fn to_pt(self) -> f64 { self.points } - - /// Convert this length into millimeters. - pub fn to_mm(self) -> f64 { self.points * 0.352778 } - - /// Convert this length into centimeters. - pub fn to_cm(self) -> f64 { self.points * 0.0352778 } - - /// Convert this length into inches. - pub fn to_inches(self) -> f64 { self.points * 0.0138889 } - - /// The maximum of this and the other length. - pub fn max(self, other: Length) -> Length { - if self > other { self } else { other } + /// Create a length from a value with a unit. + pub const fn new(val: f64, unit: Unit) -> Self { + Self { val, unit } } - /// The minimum of this and the other length. - pub fn min(self, other: Length) -> Length { - if self <= other { self } else { other } + /// Create a length from a number of points. + pub const fn pt(pt: f64) -> Self { + Self::new(pt, Unit::Pt) } - /// Set this length to the maximum of itself and the other length. - pub fn max_eq(&mut self, other: Length) { *self = self.max(other); } + /// Create a length from a number of millimeters. + pub const fn mm(mm: f64) -> Self { + Self::new(mm, Unit::Mm) + } - /// Set this length to the minimum of itself and the other length. - pub fn min_eq(&mut self, other: Length) { *self = self.min(other); } + /// Create a length from a number of centimeters. + pub const fn cm(cm: f64) -> Self { + Self::new(cm, Unit::Cm) + } - /// The anchor position along the given direction for an item with the given - /// alignment in a container with this length. - pub fn anchor(self, alignment: Alignment, direction: Direction) -> Length { - match (direction.is_positive(), alignment) { - (true, Origin) | (false, End) => Length::ZERO, - (_, Center) => self / 2, - (true, End) | (false, Origin) => self, + /// Create a length from a number of inches. + pub const fn inches(inches: f64) -> Self { + Self::new(inches, Unit::In) + } + + /// Create a length from a number of raw units. + pub const fn raw(raw: f64) -> Self { + Self::new(raw, Unit::Raw) + } + + /// Convert this to a number of points. + pub fn as_pt(self) -> f64 { + self.with_unit(Unit::Pt).val + } + + /// Convert this to a number of millimeters. + pub fn as_mm(self) -> f64 { + self.with_unit(Unit::Mm).val + } + + /// Convert this to a number of centimeters. + pub fn as_cm(self) -> f64 { + self.with_unit(Unit::Cm).val + } + + /// Convert this to a number of inches. + pub fn as_inches(self) -> f64 { + self.with_unit(Unit::In).val + } + + /// Get the value of this length in raw units. + pub fn as_raw(self) -> f64 { + self.with_unit(Unit::Raw).val + } + + /// Convert this to a length with a different unit. + pub fn with_unit(self, unit: Unit) -> Length { + Self { + val: self.val * self.unit.raw_scale() / unit.raw_scale(), + unit, + } + } +} + +impl Unit { + /// How many raw units correspond to a value of `1.0` in this unit. + fn raw_scale(self) -> f64 { + match self { + Unit::Pt => 1.0, + Unit::Mm => 2.83465, + Unit::Cm => 28.3465, + Unit::In => 72.0, + Unit::Raw => 1.0, } } } impl Display for Length { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}pt", self.points) + write!(f, "{:.2}{}", self.val, self.unit) } } @@ -83,18 +117,63 @@ impl Debug for Length { } } -impl Neg for Length { - type Output = Length; - - fn neg(self) -> Length { - Length { points: -self.points } +impl Display for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Unit::Mm => "mm", + Unit::Pt => "pt", + Unit::Cm => "cm", + Unit::In => "in", + Unit::Raw => "rw", + }) } } -impl Sum for Length { - fn sum(iter: I) -> Length - where I: Iterator { - iter.fold(Length::ZERO, Add::add) +impl Debug for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl FromStr for Length { + type Err = ParseLengthError; + + fn from_str(src: &str) -> Result { + let len = src.len(); + + // We need at least some number and the unit. + if len <= 2 { + return Err(ParseLengthError); + } + + // We can view the string as bytes since a multibyte UTF-8 char cannot + // have valid ASCII chars as subbytes. + let split = len - 2; + let bytes = src.as_bytes(); + let unit = match &bytes[split..] { + b"pt" => Unit::Pt, + b"mm" => Unit::Mm, + b"cm" => Unit::Cm, + b"in" => Unit::In, + _ => return Err(ParseLengthError), + }; + + src[..split] + .parse::() + .map(|val| Length::new(val, unit)) + .map_err(|_| ParseLengthError) + } +} + +/// The error when parsing a length fails. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct ParseLengthError; + +impl std::error::Error for ParseLengthError {} + +impl Display for ParseLengthError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad("invalid string for length") } } @@ -108,10 +187,10 @@ pub enum ScaleLength { impl ScaleLength { /// Use the absolute value or scale the entity. - pub fn scaled(&self, entity: Length) -> Length { - match self { - ScaleLength::Absolute(s) => *s, - ScaleLength::Scaled(s) => *s * entity, + pub fn raw_scaled(&self, entity: f64) -> f64 { + match *self { + ScaleLength::Absolute(l) => l.as_raw(), + ScaleLength::Scaled(s) => s * entity, } } } @@ -131,344 +210,27 @@ impl Debug for ScaleLength { } } -/// A value in two dimensions. -#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)] -pub struct Value2 { - /// The horizontal component. - pub x: T, - /// The vertical component. - pub y: T, -} +#[cfg(test)] +mod tests { + use super::*; -impl Value2 { - /// Create a new 2D-value from two values. - pub fn new(x: T, y: T) -> Value2 { Value2 { x, y } } - - /// Create a new 2D-value with `x` set to a value and `y` to default. - pub fn with_x(x: T) -> Value2 where T: Default { - Value2 { x, y: T::default() } + #[test] + fn test_length_from_str_parses_correct_value_and_unit() { + assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5))); } - /// Create a new 2D-value with `y` set to a value and `x` to default. - pub fn with_y(y: T) -> Value2 where T: Default { - Value2 { x: T::default(), y } + #[test] + fn test_length_from_str_works_with_non_ascii_chars() { + assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError)); } - /// Create a new 2D-value with the primary axis set to a value and the other - /// one to default. - pub fn with_primary(v: T, axes: LayoutAxes) -> Value2 where T: Default { - Value2::with_x(v).generalized(axes) + #[test] + fn test_length_formats_correctly() { + assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); } - /// Create a new 2D-value with the secondary axis set to a value and the - /// other one to default. - pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2 where T: Default { - Value2::with_y(v).generalized(axes) - } - - /// Create a 2D-value with `x` and `y` set to the same value `s`. - pub fn with_all(s: T) -> Value2 { Value2 { x: s.clone(), y: s } } - - /// Get the specificed component. - pub fn get(self, axis: SpecificAxis) -> T { - match axis { - Horizontal => self.x, - Vertical => self.y, - } - } - - /// Borrow the specificed component mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T { - match axis { - Horizontal => &mut self.x, - Vertical => &mut self.y, - } - } - - /// Return the primary value of this specialized 2D-value. - pub fn primary(self, axes: LayoutAxes) -> T { - if axes.primary.axis() == Horizontal { self.x } else { self.y } - } - - /// Borrow the primary value of this specialized 2D-value mutably. - pub fn primary_mut(&mut self, axes: LayoutAxes) -> &mut T { - if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y } - } - - /// Return the secondary value of this specialized 2D-value. - pub fn secondary(self, axes: LayoutAxes) -> T { - if axes.primary.axis() == Horizontal { self.y } else { self.x } - } - - /// Borrow the secondary value of this specialized 2D-value mutably. - pub fn secondary_mut(&mut self, axes: LayoutAxes) -> &mut T { - if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x } - } - - /// Returns the generalized version of a `Size2D` dependent on the layouting - /// axes, 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, axes: LayoutAxes) -> Value2 { - match axes.primary.axis() { - Horizontal => self, - Vertical => Value2 { x: self.y, y: self.x }, - } - } - - /// Returns the specialized version of this generalized Size2D (inverse to - /// `generalized`). - pub fn specialized(self, axes: LayoutAxes) -> Value2 { - // In fact, generalized is its own inverse. For reasons of clarity - // at the call site, we still have this second function. - self.generalized(axes) - } - - /// Swap the `x` and `y` values. - pub fn swap(&mut self) { - std::mem::swap(&mut self.x, &mut self.y); + #[test] + fn test_length_unit_conversion() { + assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4); } } - -impl Debug for Value2 where T: Debug { - 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 2D-length. - pub const ZERO: Size = Size { x: Length::ZERO, y: Length::ZERO }; - - /// Whether the given 2D-length fits into this one, that is, both coordinate - /// values are smaller or equal. - pub fn fits(self, other: Size) -> bool { - self.x >= other.x && self.y >= other.y - } - - /// Return a 2D-length padded by the paddings of the given box. - pub fn padded(self, padding: Margins) -> Size { - Size { - x: self.x + padding.left + padding.right, - y: self.y + padding.top + padding.bottom, - } - } - - /// Return a 2D-length reduced by the paddings of the given box. - pub fn unpadded(self, padding: Margins) -> Size { - 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 - /// alignment in a container with this length. - /// - /// This assumes the length to be generalized such that `x` corresponds to the - /// primary axis. - pub fn anchor(self, alignment: LayoutAlignment, axes: LayoutAxes) -> Size { - Size { - x: self.x.anchor(alignment.primary, axes.primary), - y: self.y.anchor(alignment.secondary, axes.secondary), - } - } -} - -impl Neg for Size { - type Output = Size; - - fn neg(self) -> Size { - Size { - x: -self.x, - y: -self.y, - } - } -} - -/// A value in four dimensions. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize)] -pub struct Value4 { - /// The left extent. - pub left: T, - /// The top extent. - pub top: T, - /// The right extent. - pub right: T, - /// The bottom extent. - pub bottom: T, -} - -impl Value4 { - /// Create a new box from four sizes. - pub fn new(left: T, top: T, right: T, bottom: T) -> Value4 { - Value4 { left, top, right, bottom } - } - - /// Create a box with all four fields set to the same value `s`. - pub fn with_all(value: T) -> Value4 { - Value4 { - left: value.clone(), - top: value.clone(), - right: value.clone(), - bottom: value - } - } - - /// Get a mutable reference to the value for the specified direction at the - /// alignment. - /// - /// Center alignment is treated the same as origin alignment. - pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T { - if alignment == End { - direction = direction.inv(); - } - - match direction { - LeftToRight => &mut self.left, - RightToLeft => &mut self.right, - TopToBottom => &mut self.top, - BottomToTop => &mut self.bottom, - } - } - - /// Set all values to the given value. - pub fn set_all(&mut self, value: T) { - *self = Value4::with_all(value); - } - - /// Set the `left` and `right` values. - pub fn set_horizontal(&mut self, value: T) { - self.left = value.clone(); - self.right = value; - } - - /// Set the `top` and `bottom` values. - pub fn set_vertical(&mut self, value: T) { - self.top = value.clone(); - self.bottom = value; - } -} - -/// A length in four dimensions. -pub type Margins = Value4; - -impl Margins { - /// The zeroed length box. - pub const ZERO: Margins = Margins { - left: Length::ZERO, - top: Length::ZERO, - right: Length::ZERO, - bottom: Length::ZERO, - }; -} - -impl FromStr for Length { - type Err = ParseLengthError; - - fn from_str(src: &str) -> Result { - let func = match () { - _ if src.ends_with("pt") => Length::pt, - _ if src.ends_with("mm") => Length::mm, - _ if src.ends_with("cm") => Length::cm, - _ if src.ends_with("in") => Length::inches, - _ => return Err(ParseLengthError), - }; - - Ok(func(src[..src.len() - 2] - .parse::() - .map_err(|_| ParseLengthError)?)) - - } -} - -/// An error which can be returned when parsing a length. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct ParseLengthError; - -impl std::error::Error for ParseLengthError {} - -impl Display for ParseLengthError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("invalid string for length") - } -} - -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 i32, $ty $t $o $($rest)*);)* - $(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! { Length(self, other) [points] } -implement_size! { Size(self, other) [x, y] } diff --git a/src/lib.rs b/src/lib.rs index 8121b22ed..22ef52085 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ pub mod export; pub mod font; #[macro_use] pub mod func; +pub mod geom; pub mod layout; pub mod library; pub mod length; diff --git a/src/library/font.rs b/src/library/font.rs index 5696cf4af..a78f91241 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -157,8 +157,8 @@ function! { layout(self, ctx, f) { styled(&self.body, ctx, self.size, |t, s| { match s { - ScaleLength::Absolute(size) => { - t.base_font_size = size; + ScaleLength::Absolute(length) => { + t.base_font_size = length.as_raw(); t.font_scale = 1.0; } ScaleLength::Scaled(scale) => t.font_scale = scale, diff --git a/src/library/layout.rs b/src/library/layout.rs index 2d0e3ac5d..7d9895558 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -125,7 +125,7 @@ function! { let map = self.extents.dedup(&mut f.diagnostics, ctx.axes); for &axis in &[Horizontal, Vertical] { if let Some(scale) = map.get(axis) { - let length = scale.scaled(ctx.base.get(axis)); + let length = scale.raw_scaled(ctx.base.get(axis)); *ctx.base.get_mut(axis) = length; *ctx.spaces[0].dimensions.get_mut(axis) = length; *ctx.spaces[0].expansion.get_mut(axis) = true; diff --git a/src/library/page.rs b/src/library/page.rs index dd63a0a74..43f916b33 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -31,8 +31,8 @@ function! { } let map = self.extents.dedup(&mut f.diagnostics, ctx.axes); - map.with(Horizontal, |&width| style.dimensions.x = width); - map.with(Vertical, |&height| style.dimensions.y = height); + map.with(Horizontal, |&width| style.dimensions.x = width.as_raw()); + map.with(Vertical, |&height| style.dimensions.y = height.as_raw()); if self.flip { style.dimensions.swap(); diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 5ae25a926..c632bdb4a 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -98,7 +98,7 @@ function! { layout(self, ctx, f) { if let Some((axis, spacing)) = self.spacing { let axis = axis.to_generic(ctx.axes); - let spacing = spacing.scaled(ctx.style.text.font_size()); + let spacing = spacing.raw_scaled(ctx.style.text.font_size()); vec![AddSpacing(spacing, SpacingKind::Hard, axis)] } else { vec![] diff --git a/src/paper.rs b/src/paper.rs index ba2dc212c..eb059090e 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -1,6 +1,7 @@ //! Predefined papers. -use crate::length::{Length, Size, Value4, ScaleLength}; +use crate::geom::{Size, Value4}; +use crate::length::{Length, ScaleLength}; /// Specification of a paper. #[derive(Debug, Copy, Clone, PartialEq)] @@ -21,7 +22,7 @@ impl Paper { /// The size of the paper. pub fn size(self) -> Size { - Size::new(self.width, self.height) + Size::new(self.width.as_raw(), self.height.as_raw()) } } @@ -74,8 +75,8 @@ macro_rules! papers { #[doc = $names] #[doc = "`."] pub const $var: Paper = Paper { - width: Length { points: 2.83465 * $width }, - height: Length { points: 2.83465 * $height }, + width: Length::mm($width), + height: Length::mm($height), class: PaperClass::$class, }; }; diff --git a/src/style.rs b/src/style.rs index ca05d68f0..0490ef07a 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,8 @@ //! Styles for text and pages. use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth}; -use crate::length::{Length, Size, Margins, Value4, ScaleLength}; +use crate::geom::{Size, Margins, Value4}; +use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass, PAPER_A4}; /// Defines properties of pages and text. @@ -27,7 +28,7 @@ pub struct TextStyle { /// whether the next `_` makes italic or non-italic. pub italic: bool, /// The base font size. - pub base_font_size: Length, + pub base_font_size: f64, /// The font scale to apply on the base font size. pub font_scale: f64, /// The word spacing (as a multiple of the font size). @@ -40,22 +41,22 @@ pub struct TextStyle { impl TextStyle { /// The scaled font size. - pub fn font_size(&self) -> Length { + pub fn font_size(&self) -> f64 { self.base_font_size * self.font_scale } /// The absolute word spacing. - pub fn word_spacing(&self) -> Length { + pub fn word_spacing(&self) -> f64 { self.word_spacing_scale * self.font_size() } /// The absolute line spacing. - pub fn line_spacing(&self) -> Length { + pub fn line_spacing(&self) -> f64 { (self.line_spacing_scale - 1.0) * self.font_size() } /// The absolute paragraph spacing. - pub fn paragraph_spacing(&self) -> Length { + pub fn paragraph_spacing(&self) -> f64 { (self.paragraph_spacing_scale - 1.0) * self.font_size() } } @@ -81,7 +82,7 @@ impl Default for TextStyle { }, bolder: false, italic: false, - base_font_size: Length::pt(11.0), + base_font_size: Length::pt(11.0).as_raw(), font_scale: 1.0, word_spacing_scale: 0.25, line_spacing_scale: 1.2, @@ -118,10 +119,10 @@ impl PageStyle { let default = self.class.default_margins(); Margins { - left: self.margins.left.unwrap_or(default.left).scaled(dims.x), - top: self.margins.top.unwrap_or(default.top).scaled(dims.y), - right: self.margins.right.unwrap_or(default.right).scaled(dims.x), - bottom: self.margins.bottom.unwrap_or(default.bottom).scaled(dims.y), + left: self.margins.left.unwrap_or(default.left).raw_scaled(dims.x), + top: self.margins.top.unwrap_or(default.top).raw_scaled(dims.y), + right: self.margins.right.unwrap_or(default.right).raw_scaled(dims.x), + bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(dims.y), } } } diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs index 2ac702233..59159ae11 100644 --- a/src/syntax/func/maps.rs +++ b/src/syntax/func/maps.rs @@ -1,8 +1,9 @@ //! Deduplicating maps and keys for argument parsing. use crate::diagnostic::Diagnostics; +use crate::geom::Value4; use crate::layout::prelude::*; -use crate::length::{ScaleLength, Value4}; +use crate::length::ScaleLength; use crate::syntax::span::Spanned; use super::keys::*; use super::values::*; diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs index 85891d5e8..64d4d345d 100644 --- a/src/syntax/func/values.rs +++ b/src/syntax/func/values.rs @@ -76,13 +76,13 @@ value!(Ident, "identifier", Expr::Ident(i) => i); value!(String, "string", Expr::Str(s) => s); value!(f64, "number", Expr::Number(n) => n); value!(bool, "bool", Expr::Bool(b) => b); -value!(Length, "length", Expr::Length(s) => s); +value!(Length, "length", Expr::Length(l) => l); value!(Tuple, "tuple", Expr::Tuple(t) => t); value!(Object, "object", Expr::Object(o) => o); value!(ScaleLength, "number or length", Expr::Length(length) => ScaleLength::Absolute(length), - Expr::Number(scale) => ScaleLength::Scaled(scale as f64), + Expr::Number(scale) => ScaleLength::Scaled(scale), ); /// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 9bb95c979..4998ea19e 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -534,6 +534,7 @@ pub fn is_identifier(string: &str) -> bool { #[cfg(test)] #[allow(non_snake_case)] mod tests { + use crate::length::Length; use super::super::test::check; use super::*; use Token::{ diff --git a/tests/src/render.py b/tests/src/render.py index 3b6c96f88..80f165a1d 100644 --- a/tests/src/render.py +++ b/tests/src/render.py @@ -170,8 +170,9 @@ class BoxRenderer: return self.img -def pix(points): - return int(4 * points) +# the number of pixels per raw unit +def pix(raw): + return int(4 * raw) def overlap(a, b): return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3]) diff --git a/tests/src/typeset.rs b/tests/src/typeset.rs index af73bd025..ccce8820d 100644 --- a/tests/src/typeset.rs +++ b/tests/src/typeset.rs @@ -14,8 +14,9 @@ use futures_executor::block_on; use typstc::Typesetter; use typstc::font::DynProvider; +use typstc::geom::{Size, Value4}; use typstc::layout::MultiLayout; -use typstc::length::{Length, Size, Value4}; +use typstc::length::Length; use typstc::style::PageStyle; use typstc::paper::PaperClass; use typstc::export::pdf; @@ -85,7 +86,7 @@ fn test(name: &str, src: &str, index: &FsIndex) -> DynResult<()> { typesetter.set_page_style(PageStyle { class: PaperClass::Custom, - dimensions: Size::with_all(Length::pt(250.0)), + dimensions: Size::with_all(Length::pt(250.0).as_raw()), margins: Value4::with_all(None), });