summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/export/pdf.rs25
-rw-r--r--src/geom.rs324
-rw-r--r--src/layout/actions.rs10
-rw-r--r--src/layout/line.rs21
-rw-r--r--src/layout/mod.rs14
-rw-r--r--src/layout/model.rs4
-rw-r--r--src/layout/stack.rs18
-rw-r--r--src/layout/text.rs17
-rw-r--r--src/length.rs546
-rw-r--r--src/lib.rs1
-rw-r--r--src/library/font.rs4
-rw-r--r--src/library/layout.rs2
-rw-r--r--src/library/page.rs4
-rw-r--r--src/library/spacing.rs2
-rw-r--r--src/paper.rs9
-rw-r--r--src/style.rs23
-rw-r--r--src/syntax/func/maps.rs3
-rw-r--r--src/syntax/func/values.rs4
-rw-r--r--src/syntax/tokens.rs1
-rw-r--r--tests/src/render.py5
-rw-r--r--tests/src/typeset.rs5
21 files changed, 567 insertions, 475 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index e771617a..f06e06af 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 00000000..dcac2000
--- /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<T> {
+ /// The horizontal component.
+ pub x: T,
+ /// The vertical component.
+ pub y: T,
+}
+
+impl<T: Clone> Value2<T> {
+ /// Create a new 2D-value from two values.
+ pub fn new(x: T, y: T) -> Value2<T> { 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<T> 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<T> 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<T> 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<T> 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<T> { 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<T> {
+ 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<T> {
+ // 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<T> Debug for Value2<T> 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<f64>;
+
+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<T> {
+ /// 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<T: Clone> Value4<T> {
+ /// Create a new box from four sizes.
+ pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> {
+ 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<T> {
+ 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<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] }
diff --git a/src/layout/actions.rs b/src/layout/actions.rs
index 7806932e..89c10285 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<LayoutAction>,
- active_font: (FaceId, Length),
+ active_font: (FaceId, f64),
next_pos: Option<Size>,
- 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 1bb36204..0ef58878 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<LayoutAlignment>,
/// 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<Length>,
+ usable: Option<f64>,
/// 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 8bcceda6..a6af0f82 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 3fb594d5..c78c733e 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 20d99fa6..2dd67ea9 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 22616667..30995be0 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 8131a6e9..4c14c894 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 a value with a unit.
+ pub const fn new(val: f64, unit: Unit) -> Self {
+ Self { val, unit }
+ }
- /// Create a length from an amount of millimeters.
- pub fn mm(mm: f64) -> Length { Length { points: 2.83465 * mm } }
+ /// Create a length from a number of points.
+ pub const fn pt(pt: f64) -> Self {
+ Self::new(pt, Unit::Pt)
+ }
- /// Create a length from an amount of centimeters.
- pub fn cm(cm: f64) -> Length { Length { points: 28.3465 * cm } }
+ /// Create a length from a number of millimeters.
+ pub const fn mm(mm: f64) -> Self {
+ Self::new(mm, Unit::Mm)
+ }
- /// Create a length from an amount of inches.
- pub fn inches(inches: f64) -> Length { Length { points: 72.0 * inches } }
+ /// Create a length from a number of centimeters.
+ pub const fn cm(cm: f64) -> Self {
+ Self::new(cm, Unit::Cm)
+ }
- /// Convert this length into points.
- pub fn to_pt(self) -> f64 { self.points }
+ /// Create a length from a number of inches.
+ pub const fn inches(inches: f64) -> Self {
+ Self::new(inches, Unit::In)
+ }
- /// Convert this length into millimeters.
- pub fn to_mm(self) -> f64 { self.points * 0.352778 }
+ /// Create a length from a number of raw units.
+ pub const fn raw(raw: f64) -> Self {
+ Self::new(raw, Unit::Raw)
+ }
- /// Convert this length into centimeters.
- pub fn to_cm(self) -> f64 { self.points * 0.0352778 }
+ /// Convert this to a number of points.
+ pub fn as_pt(self) -> f64 {
+ self.with_unit(Unit::Pt).val
+ }
- /// Convert this length into inches.
- pub fn to_inches(self) -> f64 { self.points * 0.0138889 }
+ /// Convert this to a number of millimeters.
+ pub fn as_mm(self) -> f64 {
+ self.with_unit(Unit::Mm).val
+ }
- /// The maximum of this and the other length.
- pub fn max(self, other: Length) -> Length {
- if self > other { self } else { other }
+ /// Convert this to a number of centimeters.
+ pub fn as_cm(self) -> f64 {
+ self.with_unit(Unit::Cm).val
}
- /// The minimum of this and the other length.
- pub fn min(self, other: Length) -> Length {
- if self <= other { self } else { other }
+ /// Convert this to a number of inches.
+ pub fn as_inches(self) -> f64 {
+ self.with_unit(Unit::In).val
}
- /// Set this length to the maximum of itself and the other length.
- pub fn max_eq(&mut self, other: Length) { *self = self.max(other); }
+ /// Get the value of this length in raw units.
+ pub fn as_raw(self) -> f64 {
+ self.with_unit(Unit::Raw).val
+ }
- /// Set this length to the minimum of itself and the other length.
- pub fn min_eq(&mut self, other: Length) { *self = self.min(other); }
+ /// 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,
+ }
+ }
+}
- /// 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,
+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;
+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 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<Self, Self::Err> {
+ let len = src.len();
+
+ // We need at least some number and the unit.
+ if len <= 2 {
+ return Err(ParseLengthError);
+ }
- fn neg(self) -> Length {
- Length { points: -self.points }
+ // 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::<f64>()
+ .map(|val| Length::new(val, unit))
+ .map_err(|_| ParseLengthError)
}
}
-impl Sum for Length {
- fn sum<I>(iter: I) -> Length
- where I: Iterator<Item = Length> {
- iter.fold(Length::ZERO, Add::add)
+/// 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<T> {
- /// The horizontal component.
- pub x: T,
- /// The vertical component.
- pub y: T,
-}
-
-impl<T: Clone> Value2<T> {
- /// Create a new 2D-value from two values.
- pub fn new(x: T, y: T) -> Value2<T> { 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<T> 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<T> 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<T> 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<T> 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<T> { 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 }
- }
+#[cfg(test)]
+mod tests {
+ use super::*;
- /// 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 }
+ #[test]
+ fn test_length_from_str_parses_correct_value_and_unit() {
+ assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5)));
}
- /// 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 }
+ #[test]
+ fn test_length_from_str_works_with_non_ascii_chars() {
+ assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError));
}
- /// 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<T> {
- 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<T> {
- // In fact, generalized is its own inverse. For reasons of clarity
- // at the call site, we still have this second function.
- self.generalized(axes)
+ #[test]
+ fn test_length_formats_correctly() {
+ assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
}
- /// 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<T> Debug for Value2<T> 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<Length>;
-
-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<T> {
- /// 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<T: Clone> Value4<T> {
- /// Create a new box from four sizes.
- pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> {
- 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<T> {
- 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<Length>;
-
-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<Length, ParseLengthError> {
- 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::<f64>()
- .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 8121b22e..22ef5208 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 5696cf4a..a78f9124 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 2d0e3ac5..7d989555 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 dd63a0a7..43f916b3 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 5ae25a92..c632bdb4 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 ba2dc212..eb059090 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 ca05d68f..0490ef07 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 2ac70223..59159ae1 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 85891d5e..64d4d345 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 9bb95c97..4998ea19 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 3b6c96f8..80f165a1 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 af73bd02..ccce8820 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),
});