mirror of
https://github.com/typst/typst
synced 2025-06-28 08:12:53 +08:00
Raw lengths 🚲
Replace unitless length with raw f64 and introduce length type with unit.
This commit is contained in:
parent
04c05502be
commit
659248d52f
@ -116,8 +116,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
let rect = Rect::new(
|
let rect = Rect::new(
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
page.dimensions.x.to_pt() as f32,
|
Length::raw(page.dimensions.x).as_pt() as f32,
|
||||||
page.dimensions.y.to_pt() as f32,
|
Length::raw(page.dimensions.y).as_pt() as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.writer.write_obj(
|
self.writer.write_obj(
|
||||||
@ -145,7 +145,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
// needed.
|
// needed.
|
||||||
let mut text = Text::new();
|
let mut text = Text::new();
|
||||||
let mut face_id = FaceId::MAX;
|
let mut face_id = FaceId::MAX;
|
||||||
let mut font_size = Length::ZERO;
|
let mut font_size = 0.0;
|
||||||
let mut next_pos = None;
|
let mut next_pos = None;
|
||||||
|
|
||||||
for action in &page.actions {
|
for action in &page.actions {
|
||||||
@ -157,13 +157,16 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
&LayoutAction::SetFont(id, size) => {
|
&LayoutAction::SetFont(id, size) => {
|
||||||
face_id = id;
|
face_id = id;
|
||||||
font_size = size;
|
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) => {
|
LayoutAction::WriteText(string) => {
|
||||||
if let Some(pos) = next_pos.take() {
|
if let Some(pos) = next_pos.take() {
|
||||||
let x = pos.x.to_pt();
|
let x = Length::raw(pos.x).as_pt();
|
||||||
let y = (page.dimensions.y - pos.y - font_size).to_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);
|
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 base_font = format!("ABCDEF+{}", name);
|
||||||
let system_info = CIDSystemInfo::new("Adobe", "Identity", 0);
|
let system_info = CIDSystemInfo::new("Adobe", "Identity", 0);
|
||||||
|
|
||||||
let units_per_em = face.units_per_em().unwrap_or(1000);
|
let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
|
||||||
let ratio = 1.0 / (units_per_em as f64);
|
let ratio = 1.0 / units_per_em;
|
||||||
let to_length = |x| Length::pt(ratio * x as f64);
|
let to_glyph_unit = |font_unit: f64| {
|
||||||
let to_glyph_unit = |font_unit| {
|
(1000.0 * ratio * font_unit).round() as GlyphUnit
|
||||||
let length = to_length(font_unit);
|
|
||||||
(1000.0 * length.to_pt()).round() as GlyphUnit
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let global_bbox = face.global_bounding_box();
|
let global_bbox = face.global_bounding_box();
|
||||||
|
324
src/geom.rs
Normal file
324
src/geom.rs
Normal file
@ -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] }
|
@ -4,7 +4,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use serde::ser::{Serialize, Serializer, SerializeTuple};
|
use serde::ser::{Serialize, Serializer, SerializeTuple};
|
||||||
use fontdock::FaceId;
|
use fontdock::FaceId;
|
||||||
|
|
||||||
use crate::length::{Length, Size};
|
use crate::geom::Size;
|
||||||
use super::Layout;
|
use super::Layout;
|
||||||
use self::LayoutAction::*;
|
use self::LayoutAction::*;
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ pub enum LayoutAction {
|
|||||||
/// Move to an absolute position.
|
/// Move to an absolute position.
|
||||||
MoveAbsolute(Size),
|
MoveAbsolute(Size),
|
||||||
/// Set the font given the index from the font loader and font 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.
|
/// Write text at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Visualize a box for debugging purposes.
|
/// Visualize a box for debugging purposes.
|
||||||
@ -82,9 +82,9 @@ impl Debug for LayoutAction {
|
|||||||
pub struct LayoutActions {
|
pub struct LayoutActions {
|
||||||
origin: Size,
|
origin: Size,
|
||||||
actions: Vec<LayoutAction>,
|
actions: Vec<LayoutAction>,
|
||||||
active_font: (FaceId, Length),
|
active_font: (FaceId, f64),
|
||||||
next_pos: Option<Size>,
|
next_pos: Option<Size>,
|
||||||
next_font: Option<(FaceId, Length)>,
|
next_font: Option<(FaceId, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutActions {
|
impl LayoutActions {
|
||||||
@ -93,7 +93,7 @@ impl LayoutActions {
|
|||||||
LayoutActions {
|
LayoutActions {
|
||||||
actions: vec![],
|
actions: vec![],
|
||||||
origin: Size::ZERO,
|
origin: Size::ZERO,
|
||||||
active_font: (FaceId::MAX, Length::ZERO),
|
active_font: (FaceId::MAX, 0.0),
|
||||||
next_pos: None,
|
next_pos: None,
|
||||||
next_font: None,
|
next_font: None,
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ pub struct LineContext {
|
|||||||
/// extent of the layout.
|
/// extent of the layout.
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
/// The line spacing.
|
/// 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
|
/// A line run is a sequence of boxes with the same alignment that are arranged
|
||||||
@ -48,9 +48,8 @@ pub struct LineContext {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LineRun {
|
struct LineRun {
|
||||||
/// The so-far accumulated layouts in the line.
|
/// The so-far accumulated layouts in the line.
|
||||||
layouts: Vec<(Length, Layout)>,
|
layouts: Vec<(f64, Layout)>,
|
||||||
/// The width (primary length) and maximal height (secondary length) of the
|
/// The width and maximal height of the line.
|
||||||
/// line.
|
|
||||||
size: Size,
|
size: Size,
|
||||||
/// The alignment of all layouts in the line.
|
/// The alignment of all layouts in the line.
|
||||||
///
|
///
|
||||||
@ -60,7 +59,7 @@ struct LineRun {
|
|||||||
alignment: Option<LayoutAlignment>,
|
alignment: Option<LayoutAlignment>,
|
||||||
/// If another line run with different alignment already took up some space
|
/// 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.
|
/// 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.
|
/// A possibly cached soft spacing or spacing state.
|
||||||
last_spacing: LastSpacing,
|
last_spacing: LastSpacing,
|
||||||
}
|
}
|
||||||
@ -104,7 +103,7 @@ impl LineLayouter {
|
|||||||
let usable = self.stack.usable().primary(axes);
|
let usable = self.stack.usable().primary(axes);
|
||||||
rest_run.usable = Some(match layout.alignment.primary {
|
rest_run.usable = Some(match layout.alignment.primary {
|
||||||
Alignment::Origin => unreachable!("origin > x"),
|
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,
|
Alignment::End => usable - self.run.size.x,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -138,7 +137,7 @@ impl LineLayouter {
|
|||||||
self.run.layouts.push((self.run.size.x, layout));
|
self.run.layouts.push((self.run.size.x, layout));
|
||||||
|
|
||||||
self.run.size.x += size.x;
|
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;
|
self.run.last_spacing = LastSpacing::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,11 +169,11 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add spacing along the primary axis to the line.
|
/// 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 {
|
match kind {
|
||||||
// A hard space is simply an empty box.
|
// A hard space is simply an empty box.
|
||||||
SpacingKind::Hard => {
|
SpacingKind::Hard => {
|
||||||
spacing.min_eq(self.usable().x);
|
spacing = spacing.min(self.usable().x);
|
||||||
self.run.size.x += spacing;
|
self.run.size.x += spacing;
|
||||||
self.run.last_spacing = LastSpacing::Hard;
|
self.run.last_spacing = LastSpacing::Hard;
|
||||||
}
|
}
|
||||||
@ -196,7 +195,7 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the line and add secondary spacing to the underlying stack.
|
/// 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.finish_line_if_not_empty();
|
||||||
self.stack.add_spacing(spacing, kind)
|
self.stack.add_spacing(spacing, kind)
|
||||||
}
|
}
|
||||||
@ -218,7 +217,7 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update the line spacing.
|
/// 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;
|
self.ctx.line_spacing = line_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use smallvec::SmallVec;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use fontdock::FaceId;
|
use fontdock::FaceId;
|
||||||
|
|
||||||
use crate::length::{Length, Size, Margins};
|
use crate::geom::{Size, Margins};
|
||||||
use self::prelude::*;
|
use self::prelude::*;
|
||||||
|
|
||||||
pub mod line;
|
pub mod line;
|
||||||
@ -219,8 +219,8 @@ impl Direction {
|
|||||||
///
|
///
|
||||||
/// - `1` if the direction is positive.
|
/// - `1` if the direction is positive.
|
||||||
/// - `-1` if the direction is negative.
|
/// - `-1` if the direction is negative.
|
||||||
pub fn factor(self) -> i32 {
|
pub fn factor(self) -> f64 {
|
||||||
if self.is_positive() { 1 } else { -1 }
|
if self.is_positive() { 1.0 } else { -1.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The inverse axis.
|
/// The inverse axis.
|
||||||
@ -368,17 +368,17 @@ enum LastSpacing {
|
|||||||
/// The last item was hard spacing.
|
/// The last item was hard spacing.
|
||||||
Hard,
|
Hard,
|
||||||
/// The last item was soft spacing with the given width and level.
|
/// The last item was soft spacing with the given width and level.
|
||||||
Soft(Length, u32),
|
Soft(f64, u32),
|
||||||
/// The last item was not spacing.
|
/// The last item was not spacing.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LastSpacing {
|
impl LastSpacing {
|
||||||
/// The length of the soft space if this is a soft space or zero otherwise.
|
/// The width of the soft space if this is a soft space or zero otherwise.
|
||||||
fn soft_or_zero(self) -> Length {
|
fn soft_or_zero(self) -> f64 {
|
||||||
match self {
|
match self {
|
||||||
LastSpacing::Soft(space, _) => space,
|
LastSpacing::Soft(space, _) => space,
|
||||||
_ => Length::ZERO,
|
_ => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use smallvec::smallvec;
|
|||||||
use crate::{Pass, Feedback};
|
use crate::{Pass, Feedback};
|
||||||
use crate::SharedFontLoader;
|
use crate::SharedFontLoader;
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
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::{Model, SyntaxModel, Node, Decoration};
|
||||||
use crate::syntax::span::{Span, Spanned};
|
use crate::syntax::span::{Span, Spanned};
|
||||||
use super::line::{LineLayouter, LineContext};
|
use super::line::{LineLayouter, LineContext};
|
||||||
@ -74,7 +74,7 @@ pub enum Command<'a> {
|
|||||||
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
||||||
/// secondary axis. The spacing kind defines how the spacing interacts with
|
/// secondary axis. The spacing kind defines how the spacing interacts with
|
||||||
/// surrounding spacing.
|
/// surrounding spacing.
|
||||||
AddSpacing(Length, SpacingKind, GenericAxis),
|
AddSpacing(f64, SpacingKind, GenericAxis),
|
||||||
|
|
||||||
/// Start a new line.
|
/// Start a new line.
|
||||||
BreakLine,
|
BreakLine,
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
//! sentence in the second box.
|
//! sentence in the second box.
|
||||||
|
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use crate::length::Value4;
|
use crate::geom::Value4;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Performs the stack layouting.
|
/// Performs the stack layouting.
|
||||||
@ -128,12 +128,12 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add secondary spacing to the stack.
|
/// 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 {
|
match kind {
|
||||||
// A hard space is simply an empty box.
|
// A hard space is simply an empty box.
|
||||||
SpacingKind::Hard => {
|
SpacingKind::Hard => {
|
||||||
// Reduce the spacing such that it definitely fits.
|
// 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);
|
let dimensions = Size::with_y(spacing);
|
||||||
|
|
||||||
self.update_metrics(dimensions);
|
self.update_metrics(dimensions);
|
||||||
@ -170,11 +170,11 @@ impl StackLayouter {
|
|||||||
let mut size = self.space.size.generalized(axes);
|
let mut size = self.space.size.generalized(axes);
|
||||||
let mut extra = self.space.extra.generalized(axes);
|
let mut extra = self.space.extra.generalized(axes);
|
||||||
|
|
||||||
size.x += (dimensions.x - extra.x).max(Length::ZERO);
|
size.x += (dimensions.x - extra.x).max(0.0);
|
||||||
size.y += (dimensions.y - extra.y).max(Length::ZERO);
|
size.y += (dimensions.y - extra.y).max(0.0);
|
||||||
|
|
||||||
extra.x.max_eq(dimensions.x);
|
extra.x = extra.x.max(dimensions.x);
|
||||||
extra.y = (extra.y - dimensions.y).max(Length::ZERO);
|
extra.y = (extra.y - dimensions.y).max(0.0);
|
||||||
|
|
||||||
self.space.size = size.specialized(axes);
|
self.space.size = size.specialized(axes);
|
||||||
self.space.extra = extra.specialized(axes);
|
self.space.extra = extra.specialized(axes);
|
||||||
@ -348,7 +348,7 @@ impl StackLayouter {
|
|||||||
// is reset for this new axis-aligned run.
|
// is reset for this new axis-aligned run.
|
||||||
if rotation != axes.secondary.axis() {
|
if rotation != axes.secondary.axis() {
|
||||||
extent.y = extent.x;
|
extent.y = extent.x;
|
||||||
extent.x = Length::ZERO;
|
extent.x = 0.0;
|
||||||
rotation = axes.secondary.axis();
|
rotation = axes.secondary.axis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,7 +360,7 @@ impl StackLayouter {
|
|||||||
|
|
||||||
// Then, we add this layout's secondary extent to the accumulator.
|
// Then, we add this layout's secondary extent to the accumulator.
|
||||||
let size = layout.dimensions.generalized(*axes);
|
let size = layout.dimensions.generalized(*axes);
|
||||||
extent.x.max_eq(size.x);
|
extent.x = extent.x.max(size.x);
|
||||||
extent.y += size.y;
|
extent.y += size.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use fontdock::{FaceId, FaceQuery, FontStyle};
|
use fontdock::{FaceId, FaceQuery, FontStyle};
|
||||||
use crate::font::SharedFontLoader;
|
use crate::font::SharedFontLoader;
|
||||||
use crate::length::{Length, Size};
|
use crate::geom::Size;
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ struct TextLayouter<'a> {
|
|||||||
actions: LayoutActions,
|
actions: LayoutActions,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
active_font: FaceId,
|
active_font: FaceId,
|
||||||
width: Length,
|
width: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for text layouting.
|
/// The context for text layouting.
|
||||||
@ -51,7 +51,7 @@ impl<'a> TextLayouter<'a> {
|
|||||||
actions: LayoutActions::new(),
|
actions: LayoutActions::new(),
|
||||||
buffer: String::new(),
|
buffer: String::new(),
|
||||||
active_font: FaceId::MAX,
|
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
|
/// Select the best font for a character and return its index along with
|
||||||
/// the width of the char in the font.
|
/// 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 loader = self.ctx.loader.borrow_mut();
|
||||||
|
|
||||||
let mut variant = self.ctx.style.variant;
|
let mut variant = self.ctx.style.variant;
|
||||||
@ -132,14 +132,13 @@ impl<'a> TextLayouter<'a> {
|
|||||||
|
|
||||||
if let Some((id, face)) = loader.query(query).await {
|
if let Some((id, face)) = loader.query(query).await {
|
||||||
// Determine the width of the char.
|
// Determine the width of the char.
|
||||||
let units_per_em = face.units_per_em().unwrap_or(1000);
|
let units_per_em = face.units_per_em().unwrap_or(1000) as f64;
|
||||||
let ratio = 1.0 / (units_per_em as f64);
|
let ratio = 1.0 / units_per_em;
|
||||||
let to_length = |x| Length::pt(ratio * x as f64);
|
let to_raw = |x| ratio * x as f64;
|
||||||
|
|
||||||
let glyph = face.glyph_index(c)?;
|
let glyph = face.glyph_index(c)?;
|
||||||
let glyph_width = face.glyph_hor_advance(glyph)?;
|
let glyph_width = face.glyph_hor_advance(glyph)?;
|
||||||
let char_width = to_length(glyph_width)
|
let char_width = to_raw(glyph_width) * self.ctx.style.font_size();
|
||||||
* self.ctx.style.font_size().to_pt();
|
|
||||||
|
|
||||||
Some((id, char_width))
|
Some((id, char_width))
|
||||||
} else {
|
} else {
|
||||||
|
564
src/length.rs
564
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::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::iter::Sum;
|
|
||||||
use std::ops::*;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::layout::prelude::*;
|
/// A length with a unit.
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
/// A general spacing type.
|
|
||||||
#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct Length {
|
pub struct Length {
|
||||||
/// The length in typographic points (1/72 inches).
|
/// The length in the given unit.
|
||||||
pub points: f64,
|
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 {
|
impl Length {
|
||||||
/// The zeroed length.
|
/// Create a length from a value with a unit.
|
||||||
pub const ZERO: Length = Length { points: 0.0 };
|
pub const fn new(val: f64, unit: Unit) -> Self {
|
||||||
|
Self { val, unit }
|
||||||
/// 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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The minimum of this and the other length.
|
/// Create a length from a number of points.
|
||||||
pub fn min(self, other: Length) -> Length {
|
pub const fn pt(pt: f64) -> Self {
|
||||||
if self <= other { self } else { other }
|
Self::new(pt, Unit::Pt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set this length to the maximum of itself and the other length.
|
/// Create a length from a number of millimeters.
|
||||||
pub fn max_eq(&mut self, other: Length) { *self = self.max(other); }
|
pub const fn mm(mm: f64) -> Self {
|
||||||
|
Self::new(mm, Unit::Mm)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set this length to the minimum of itself and the other length.
|
/// Create a length from a number of centimeters.
|
||||||
pub fn min_eq(&mut self, other: Length) { *self = self.min(other); }
|
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
|
/// Create a length from a number of inches.
|
||||||
/// alignment in a container with this length.
|
pub const fn inches(inches: f64) -> Self {
|
||||||
pub fn anchor(self, alignment: Alignment, direction: Direction) -> Length {
|
Self::new(inches, Unit::In)
|
||||||
match (direction.is_positive(), alignment) {
|
}
|
||||||
(true, Origin) | (false, End) => Length::ZERO,
|
|
||||||
(_, Center) => self / 2,
|
/// Create a length from a number of raw units.
|
||||||
(true, End) | (false, Origin) => self,
|
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 {
|
impl Display for Length {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
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 {
|
impl Display for Unit {
|
||||||
type Output = Length;
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
fn neg(self) -> Length {
|
Unit::Mm => "mm",
|
||||||
Length { points: -self.points }
|
Unit::Pt => "pt",
|
||||||
|
Unit::Cm => "cm",
|
||||||
|
Unit::In => "in",
|
||||||
|
Unit::Raw => "rw",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sum for Length {
|
impl Debug for Unit {
|
||||||
fn sum<I>(iter: I) -> Length
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
where I: Iterator<Item = Length> {
|
Display::fmt(self, f)
|
||||||
iter.fold(Length::ZERO, Add::add)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
impl ScaleLength {
|
||||||
/// Use the absolute value or scale the entity.
|
/// Use the absolute value or scale the entity.
|
||||||
pub fn scaled(&self, entity: Length) -> Length {
|
pub fn raw_scaled(&self, entity: f64) -> f64 {
|
||||||
match self {
|
match *self {
|
||||||
ScaleLength::Absolute(s) => *s,
|
ScaleLength::Absolute(l) => l.as_raw(),
|
||||||
ScaleLength::Scaled(s) => *s * entity,
|
ScaleLength::Scaled(s) => s * entity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,344 +210,27 @@ impl Debug for ScaleLength {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value in two dimensions.
|
#[cfg(test)]
|
||||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Serialize)]
|
mod tests {
|
||||||
pub struct Value2<T> {
|
use super::*;
|
||||||
/// The horizontal component.
|
|
||||||
pub x: T,
|
|
||||||
/// The vertical component.
|
|
||||||
pub y: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Clone> Value2<T> {
|
#[test]
|
||||||
/// Create a new 2D-value from two values.
|
fn test_length_from_str_parses_correct_value_and_unit() {
|
||||||
pub fn new(x: T, y: T) -> Value2<T> { Value2 { x, y } }
|
assert_eq!(Length::from_str("2.5cm"), Ok(Length::cm(2.5)));
|
||||||
|
|
||||||
/// 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.
|
#[test]
|
||||||
pub fn with_y(y: T) -> Value2<T> where T: Default {
|
fn test_length_from_str_works_with_non_ascii_chars() {
|
||||||
Value2 { x: T::default(), y }
|
assert_eq!(Length::from_str("123🚚"), Err(ParseLengthError));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new 2D-value with the primary axis set to a value and the other
|
#[test]
|
||||||
/// one to default.
|
fn test_length_formats_correctly() {
|
||||||
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
|
assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string());
|
||||||
Value2::with_x(v).generalized(axes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new 2D-value with the secondary axis set to a value and the
|
#[test]
|
||||||
/// other one to default.
|
fn test_length_unit_conversion() {
|
||||||
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2<T> where T: Default {
|
assert!((Length::mm(150.0).as_cm() - 15.0) < 1e-4);
|
||||||
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<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] }
|
|
||||||
|
@ -42,6 +42,7 @@ pub mod export;
|
|||||||
pub mod font;
|
pub mod font;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod func;
|
pub mod func;
|
||||||
|
pub mod geom;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod length;
|
pub mod length;
|
||||||
|
@ -157,8 +157,8 @@ function! {
|
|||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
styled(&self.body, ctx, self.size, |t, s| {
|
styled(&self.body, ctx, self.size, |t, s| {
|
||||||
match s {
|
match s {
|
||||||
ScaleLength::Absolute(size) => {
|
ScaleLength::Absolute(length) => {
|
||||||
t.base_font_size = size;
|
t.base_font_size = length.as_raw();
|
||||||
t.font_scale = 1.0;
|
t.font_scale = 1.0;
|
||||||
}
|
}
|
||||||
ScaleLength::Scaled(scale) => t.font_scale = scale,
|
ScaleLength::Scaled(scale) => t.font_scale = scale,
|
||||||
|
@ -125,7 +125,7 @@ function! {
|
|||||||
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
||||||
for &axis in &[Horizontal, Vertical] {
|
for &axis in &[Horizontal, Vertical] {
|
||||||
if let Some(scale) = map.get(axis) {
|
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.base.get_mut(axis) = length;
|
||||||
*ctx.spaces[0].dimensions.get_mut(axis) = length;
|
*ctx.spaces[0].dimensions.get_mut(axis) = length;
|
||||||
*ctx.spaces[0].expansion.get_mut(axis) = true;
|
*ctx.spaces[0].expansion.get_mut(axis) = true;
|
||||||
|
@ -31,8 +31,8 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
let map = self.extents.dedup(&mut f.diagnostics, ctx.axes);
|
||||||
map.with(Horizontal, |&width| style.dimensions.x = width);
|
map.with(Horizontal, |&width| style.dimensions.x = width.as_raw());
|
||||||
map.with(Vertical, |&height| style.dimensions.y = height);
|
map.with(Vertical, |&height| style.dimensions.y = height.as_raw());
|
||||||
|
|
||||||
if self.flip {
|
if self.flip {
|
||||||
style.dimensions.swap();
|
style.dimensions.swap();
|
||||||
|
@ -98,7 +98,7 @@ function! {
|
|||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
if let Some((axis, spacing)) = self.spacing {
|
if let Some((axis, spacing)) = self.spacing {
|
||||||
let axis = axis.to_generic(ctx.axes);
|
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)]
|
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
vec![]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//! Predefined papers.
|
//! Predefined papers.
|
||||||
|
|
||||||
use crate::length::{Length, Size, Value4, ScaleLength};
|
use crate::geom::{Size, Value4};
|
||||||
|
use crate::length::{Length, ScaleLength};
|
||||||
|
|
||||||
/// Specification of a paper.
|
/// Specification of a paper.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@ -21,7 +22,7 @@ impl Paper {
|
|||||||
|
|
||||||
/// The size of the paper.
|
/// The size of the paper.
|
||||||
pub fn size(self) -> Size {
|
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 = $names]
|
||||||
#[doc = "`."]
|
#[doc = "`."]
|
||||||
pub const $var: Paper = Paper {
|
pub const $var: Paper = Paper {
|
||||||
width: Length { points: 2.83465 * $width },
|
width: Length::mm($width),
|
||||||
height: Length { points: 2.83465 * $height },
|
height: Length::mm($height),
|
||||||
class: PaperClass::$class,
|
class: PaperClass::$class,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
23
src/style.rs
23
src/style.rs
@ -1,7 +1,8 @@
|
|||||||
//! Styles for text and pages.
|
//! Styles for text and pages.
|
||||||
|
|
||||||
use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth};
|
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};
|
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||||
|
|
||||||
/// Defines properties of pages and text.
|
/// Defines properties of pages and text.
|
||||||
@ -27,7 +28,7 @@ pub struct TextStyle {
|
|||||||
/// whether the next `_` makes italic or non-italic.
|
/// whether the next `_` makes italic or non-italic.
|
||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
/// The base font size.
|
/// The base font size.
|
||||||
pub base_font_size: Length,
|
pub base_font_size: f64,
|
||||||
/// The font scale to apply on the base font size.
|
/// The font scale to apply on the base font size.
|
||||||
pub font_scale: f64,
|
pub font_scale: f64,
|
||||||
/// The word spacing (as a multiple of the font size).
|
/// The word spacing (as a multiple of the font size).
|
||||||
@ -40,22 +41,22 @@ pub struct TextStyle {
|
|||||||
|
|
||||||
impl TextStyle {
|
impl TextStyle {
|
||||||
/// The scaled font size.
|
/// The scaled font size.
|
||||||
pub fn font_size(&self) -> Length {
|
pub fn font_size(&self) -> f64 {
|
||||||
self.base_font_size * self.font_scale
|
self.base_font_size * self.font_scale
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute word spacing.
|
/// The absolute word spacing.
|
||||||
pub fn word_spacing(&self) -> Length {
|
pub fn word_spacing(&self) -> f64 {
|
||||||
self.word_spacing_scale * self.font_size()
|
self.word_spacing_scale * self.font_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute line spacing.
|
/// 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()
|
(self.line_spacing_scale - 1.0) * self.font_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute paragraph spacing.
|
/// 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()
|
(self.paragraph_spacing_scale - 1.0) * self.font_size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,7 +82,7 @@ impl Default for TextStyle {
|
|||||||
},
|
},
|
||||||
bolder: false,
|
bolder: false,
|
||||||
italic: false,
|
italic: false,
|
||||||
base_font_size: Length::pt(11.0),
|
base_font_size: Length::pt(11.0).as_raw(),
|
||||||
font_scale: 1.0,
|
font_scale: 1.0,
|
||||||
word_spacing_scale: 0.25,
|
word_spacing_scale: 0.25,
|
||||||
line_spacing_scale: 1.2,
|
line_spacing_scale: 1.2,
|
||||||
@ -118,10 +119,10 @@ impl PageStyle {
|
|||||||
let default = self.class.default_margins();
|
let default = self.class.default_margins();
|
||||||
|
|
||||||
Margins {
|
Margins {
|
||||||
left: self.margins.left.unwrap_or(default.left).scaled(dims.x),
|
left: self.margins.left.unwrap_or(default.left).raw_scaled(dims.x),
|
||||||
top: self.margins.top.unwrap_or(default.top).scaled(dims.y),
|
top: self.margins.top.unwrap_or(default.top).raw_scaled(dims.y),
|
||||||
right: self.margins.right.unwrap_or(default.right).scaled(dims.x),
|
right: self.margins.right.unwrap_or(default.right).raw_scaled(dims.x),
|
||||||
bottom: self.margins.bottom.unwrap_or(default.bottom).scaled(dims.y),
|
bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(dims.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
//! Deduplicating maps and keys for argument parsing.
|
//! Deduplicating maps and keys for argument parsing.
|
||||||
|
|
||||||
use crate::diagnostic::Diagnostics;
|
use crate::diagnostic::Diagnostics;
|
||||||
|
use crate::geom::Value4;
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
use crate::length::{ScaleLength, Value4};
|
use crate::length::ScaleLength;
|
||||||
use crate::syntax::span::Spanned;
|
use crate::syntax::span::Spanned;
|
||||||
use super::keys::*;
|
use super::keys::*;
|
||||||
use super::values::*;
|
use super::values::*;
|
||||||
|
@ -76,13 +76,13 @@ value!(Ident, "identifier", Expr::Ident(i) => i);
|
|||||||
value!(String, "string", Expr::Str(s) => s);
|
value!(String, "string", Expr::Str(s) => s);
|
||||||
value!(f64, "number", Expr::Number(n) => n);
|
value!(f64, "number", Expr::Number(n) => n);
|
||||||
value!(bool, "bool", Expr::Bool(b) => b);
|
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!(Tuple, "tuple", Expr::Tuple(t) => t);
|
||||||
value!(Object, "object", Expr::Object(o) => o);
|
value!(Object, "object", Expr::Object(o) => o);
|
||||||
|
|
||||||
value!(ScaleLength, "number or length",
|
value!(ScaleLength, "number or length",
|
||||||
Expr::Length(length) => ScaleLength::Absolute(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
|
/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements
|
||||||
|
@ -534,6 +534,7 @@ pub fn is_identifier(string: &str) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::length::Length;
|
||||||
use super::super::test::check;
|
use super::super::test::check;
|
||||||
use super::*;
|
use super::*;
|
||||||
use Token::{
|
use Token::{
|
||||||
|
@ -170,8 +170,9 @@ class BoxRenderer:
|
|||||||
return self.img
|
return self.img
|
||||||
|
|
||||||
|
|
||||||
def pix(points):
|
# the number of pixels per raw unit
|
||||||
return int(4 * points)
|
def pix(raw):
|
||||||
|
return int(4 * raw)
|
||||||
|
|
||||||
def overlap(a, b):
|
def overlap(a, b):
|
||||||
return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3])
|
return (a[0] < b[2] and b[0] < a[2]) and (a[1] < b[3] and b[1] < a[3])
|
||||||
|
@ -14,8 +14,9 @@ use futures_executor::block_on;
|
|||||||
|
|
||||||
use typstc::Typesetter;
|
use typstc::Typesetter;
|
||||||
use typstc::font::DynProvider;
|
use typstc::font::DynProvider;
|
||||||
|
use typstc::geom::{Size, Value4};
|
||||||
use typstc::layout::MultiLayout;
|
use typstc::layout::MultiLayout;
|
||||||
use typstc::length::{Length, Size, Value4};
|
use typstc::length::Length;
|
||||||
use typstc::style::PageStyle;
|
use typstc::style::PageStyle;
|
||||||
use typstc::paper::PaperClass;
|
use typstc::paper::PaperClass;
|
||||||
use typstc::export::pdf;
|
use typstc::export::pdf;
|
||||||
@ -85,7 +86,7 @@ fn test(name: &str, src: &str, index: &FsIndex) -> DynResult<()> {
|
|||||||
|
|
||||||
typesetter.set_page_style(PageStyle {
|
typesetter.set_page_style(PageStyle {
|
||||||
class: PaperClass::Custom,
|
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),
|
margins: Value4::with_all(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user