Refactor geom
module
This commit is contained in:
parent
66030ae5d7
commit
95e9134a3c
@ -16,7 +16,7 @@ use self::outline::{Heading, HeadingNode};
|
||||
use self::page::Page;
|
||||
use crate::font::Font;
|
||||
use crate::frame::{Frame, Lang};
|
||||
use crate::geom::{Dir, Em, Length};
|
||||
use crate::geom::{Abs, Dir, Em};
|
||||
use crate::image::Image;
|
||||
|
||||
/// Export a collection of frames into a PDF file.
|
||||
@ -182,13 +182,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for [`Length`].
|
||||
trait LengthExt {
|
||||
/// Convert an em length to a number of points.
|
||||
/// Additional methods for [`Abs`].
|
||||
trait AbsExt {
|
||||
/// Convert an to a number of points.
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl LengthExt for Length {
|
||||
impl AbsExt for Abs {
|
||||
fn to_f32(self) -> f32 {
|
||||
self.to_pt() as f32
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use pdf_writer::{Finish, Ref, TextStr};
|
||||
|
||||
use super::{LengthExt, PdfContext, RefExt};
|
||||
use crate::geom::{Length, Point};
|
||||
use super::{AbsExt, PdfContext, RefExt};
|
||||
use crate::geom::{Abs, Point};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// A heading that can later be linked in the outline panel.
|
||||
@ -77,7 +77,7 @@ pub fn write_outline_item(
|
||||
outline.title(TextStr(&node.heading.content));
|
||||
outline.dest_direct().page(node.heading.page).xyz(
|
||||
node.heading.position.x.to_f32(),
|
||||
(node.heading.position.y + Length::pt(3.0)).to_f32(),
|
||||
(node.heading.position.y + Abs::pt(3.0)).to_f32(),
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -3,12 +3,12 @@ use pdf_writer::writers::ColorSpace;
|
||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
||||
|
||||
use super::{
|
||||
deflate, EmExt, Heading, HeadingNode, LengthExt, PdfContext, RefExt, D65_GRAY, SRGB,
|
||||
deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB,
|
||||
};
|
||||
use crate::font::Font;
|
||||
use crate::frame::{Destination, Element, Frame, Group, Role, Text};
|
||||
use crate::geom::{
|
||||
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||
Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
@ -45,7 +45,7 @@ pub fn construct_page(ctx: &mut PdfContext, frame: &Frame) {
|
||||
ky: Ratio::zero(),
|
||||
kx: Ratio::zero(),
|
||||
sy: Ratio::new(-1.0),
|
||||
tx: Length::zero(),
|
||||
tx: Abs::zero(),
|
||||
ty: size.y,
|
||||
});
|
||||
|
||||
@ -169,7 +169,7 @@ struct PageContext<'a> {
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct State {
|
||||
transform: Transform,
|
||||
font: Option<(Font, Length)>,
|
||||
font: Option<(Font, Abs)>,
|
||||
fill: Option<Paint>,
|
||||
fill_space: Option<Name<'static>>,
|
||||
stroke: Option<Stroke>,
|
||||
@ -200,7 +200,7 @@ impl<'a> PageContext<'a> {
|
||||
]);
|
||||
}
|
||||
|
||||
fn set_font(&mut self, font: &Font, size: Length) {
|
||||
fn set_font(&mut self, font: &Font, size: Abs) {
|
||||
if self.state.font.as_ref().map(|(f, s)| (f, *s)) != Some((font, size)) {
|
||||
self.parent.font_map.insert(font.clone());
|
||||
let name = format_eco!("F{}", self.parent.font_map.map(font.clone()));
|
||||
@ -402,6 +402,12 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) {
|
||||
}
|
||||
|
||||
match shape.geometry {
|
||||
Geometry::Line(target) => {
|
||||
let dx = target.x.to_f32();
|
||||
let dy = target.y.to_f32();
|
||||
ctx.content.move_to(x, y);
|
||||
ctx.content.line_to(x + dx, y + dy);
|
||||
}
|
||||
Geometry::Rect(size) => {
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
@ -409,16 +415,6 @@ fn write_shape(ctx: &mut PageContext, x: f32, y: f32, shape: &Shape) {
|
||||
ctx.content.rect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
Geometry::Ellipse(size) => {
|
||||
let approx = geom::Path::ellipse(size);
|
||||
write_path(ctx, x, y, &approx);
|
||||
}
|
||||
Geometry::Line(target) => {
|
||||
let dx = target.x.to_f32();
|
||||
let dy = target.y.to_f32();
|
||||
ctx.content.move_to(x, y);
|
||||
ctx.content.line_to(x + dx, y + dy);
|
||||
}
|
||||
Geometry::Path(ref path) => {
|
||||
write_path(ctx, x, y, path);
|
||||
}
|
||||
@ -469,10 +465,10 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size)
|
||||
|
||||
/// Save a link for later writing in the annotations dictionary.
|
||||
fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size) {
|
||||
let mut min_x = Length::inf();
|
||||
let mut min_y = Length::inf();
|
||||
let mut max_x = -Length::inf();
|
||||
let mut max_y = -Length::inf();
|
||||
let mut min_x = Abs::inf();
|
||||
let mut min_y = Abs::inf();
|
||||
let mut max_x = -Abs::inf();
|
||||
let mut max_y = -Abs::inf();
|
||||
|
||||
// Compute the bounding box of the transformed link.
|
||||
for point in [
|
||||
|
@ -10,7 +10,7 @@ use usvg::FitTo;
|
||||
|
||||
use crate::frame::{Element, Frame, Group, Text};
|
||||
use crate::geom::{
|
||||
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
|
||||
self, Abs, Geometry, Paint, PathElement, Shape, Size, Stroke, Transform,
|
||||
};
|
||||
use crate::image::{DecodedImage, Image};
|
||||
|
||||
@ -286,18 +286,17 @@ fn render_shape(
|
||||
shape: &Shape,
|
||||
) -> Option<()> {
|
||||
let path = match shape.geometry {
|
||||
Geometry::Line(target) => {
|
||||
let mut builder = sk::PathBuilder::new();
|
||||
builder.line_to(target.x.to_f32(), target.y.to_f32());
|
||||
builder.finish()?
|
||||
}
|
||||
Geometry::Rect(size) => {
|
||||
let w = size.x.to_f32();
|
||||
let h = size.y.to_f32();
|
||||
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?;
|
||||
sk::PathBuilder::from_rect(rect)
|
||||
}
|
||||
Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?,
|
||||
Geometry::Line(target) => {
|
||||
let mut builder = sk::PathBuilder::new();
|
||||
builder.line_to(target.x.to_f32(), target.y.to_f32());
|
||||
builder.finish()?
|
||||
}
|
||||
Geometry::Path(ref path) => convert_path(path)?,
|
||||
};
|
||||
|
||||
@ -460,12 +459,12 @@ impl OutlineBuilder for WrappedPathBuilder {
|
||||
}
|
||||
|
||||
/// Additional methods for [`Length`].
|
||||
trait LengthExt {
|
||||
/// Convert an em length to a number of points as f32.
|
||||
trait AbsExt {
|
||||
/// Convert to a number of points as f32.
|
||||
fn to_f32(self) -> f32;
|
||||
}
|
||||
|
||||
impl LengthExt for Length {
|
||||
impl AbsExt for Abs {
|
||||
fn to_f32(self) -> f32 {
|
||||
self.to_pt() as f32
|
||||
}
|
||||
|
18
src/frame.rs
18
src/frame.rs
@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::font::Font;
|
||||
use crate::geom::{
|
||||
Align, Dir, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
||||
Abs, Align, Axes, Dir, Em, Numeric, Paint, Point, Shape, Size, Transform,
|
||||
};
|
||||
use crate::image::Image;
|
||||
use crate::model::{Dict, Value};
|
||||
@ -19,7 +19,7 @@ pub struct Frame {
|
||||
size: Size,
|
||||
/// The baseline of the frame measured from the top. If this is `None`, the
|
||||
/// frame's implicit baseline is at the bottom.
|
||||
baseline: Option<Length>,
|
||||
baseline: Option<Abs>,
|
||||
/// The semantic role of the frame.
|
||||
role: Option<Role>,
|
||||
/// The elements composing this layout.
|
||||
@ -58,22 +58,22 @@ impl Frame {
|
||||
}
|
||||
|
||||
/// The width of the frame.
|
||||
pub fn width(&self) -> Length {
|
||||
pub fn width(&self) -> Abs {
|
||||
self.size.x
|
||||
}
|
||||
|
||||
/// The height of the frame.
|
||||
pub fn height(&self) -> Length {
|
||||
pub fn height(&self) -> Abs {
|
||||
self.size.y
|
||||
}
|
||||
|
||||
/// The baseline of the frame.
|
||||
pub fn baseline(&self) -> Length {
|
||||
pub fn baseline(&self) -> Abs {
|
||||
self.baseline.unwrap_or(self.size.y)
|
||||
}
|
||||
|
||||
/// Set the frame's baseline from the top.
|
||||
pub fn set_baseline(&mut self, baseline: Length) {
|
||||
pub fn set_baseline(&mut self, baseline: Abs) {
|
||||
self.baseline = Some(baseline);
|
||||
}
|
||||
|
||||
@ -221,7 +221,7 @@ impl Frame {
|
||||
|
||||
/// Resize the frame to a new size, distributing new space according to the
|
||||
/// given alignments.
|
||||
pub fn resize(&mut self, target: Size, aligns: Spec<Align>) {
|
||||
pub fn resize(&mut self, target: Size, aligns: Axes<Align>) {
|
||||
if self.size != target {
|
||||
let offset = Point::new(
|
||||
aligns.x.position(target.x - self.size.x),
|
||||
@ -354,7 +354,7 @@ pub struct Text {
|
||||
/// The font the glyphs are contained in.
|
||||
pub font: Font,
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
pub size: Abs,
|
||||
/// Glyph color.
|
||||
pub fill: Paint,
|
||||
/// The natural language of the text.
|
||||
@ -365,7 +365,7 @@ pub struct Text {
|
||||
|
||||
impl Text {
|
||||
/// The width of the text run.
|
||||
pub fn width(&self) -> Length {
|
||||
pub fn width(&self) -> Abs {
|
||||
self.glyphs.iter().map(|g| g.x_advance).sum::<Em>().at(self.size)
|
||||
}
|
||||
}
|
||||
|
261
src/geom/abs.rs
Normal file
261
src/geom/abs.rs
Normal file
@ -0,0 +1,261 @@
|
||||
use super::*;
|
||||
|
||||
/// An absolute length.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Abs(Scalar);
|
||||
|
||||
impl Abs {
|
||||
/// The zero length.
|
||||
pub const fn zero() -> Self {
|
||||
Self(Scalar(0.0))
|
||||
}
|
||||
|
||||
/// The infinite length.
|
||||
pub const fn inf() -> Self {
|
||||
Self(Scalar(f64::INFINITY))
|
||||
}
|
||||
|
||||
/// Create an absolute length from a number of raw units.
|
||||
pub const fn raw(raw: f64) -> Self {
|
||||
Self(Scalar(raw))
|
||||
}
|
||||
|
||||
/// Create an absolute length from a value in a unit.
|
||||
pub fn with_unit(val: f64, unit: AbsUnit) -> Self {
|
||||
Self(Scalar(val * unit.raw_scale()))
|
||||
}
|
||||
|
||||
/// Create an absolute length from a number of points.
|
||||
pub fn pt(pt: f64) -> Self {
|
||||
Self::with_unit(pt, AbsUnit::Pt)
|
||||
}
|
||||
|
||||
/// Create an absolute length from a number of millimeters.
|
||||
pub fn mm(mm: f64) -> Self {
|
||||
Self::with_unit(mm, AbsUnit::Mm)
|
||||
}
|
||||
|
||||
/// Create an absolute length from a number of centimeters.
|
||||
pub fn cm(cm: f64) -> Self {
|
||||
Self::with_unit(cm, AbsUnit::Cm)
|
||||
}
|
||||
|
||||
/// Create an absolute length from a number of inches.
|
||||
pub fn inches(inches: f64) -> Self {
|
||||
Self::with_unit(inches, AbsUnit::In)
|
||||
}
|
||||
|
||||
/// Get the value of this absolute length in raw units.
|
||||
pub const fn to_raw(self) -> f64 {
|
||||
(self.0).0
|
||||
}
|
||||
|
||||
/// Get the value of this absolute length in a unit.
|
||||
pub fn to_unit(self, unit: AbsUnit) -> f64 {
|
||||
self.to_raw() / unit.raw_scale()
|
||||
}
|
||||
|
||||
/// Convert this to a number of points.
|
||||
pub fn to_pt(self) -> f64 {
|
||||
self.to_unit(AbsUnit::Pt)
|
||||
}
|
||||
|
||||
/// Convert this to a number of millimeters.
|
||||
pub fn to_mm(self) -> f64 {
|
||||
self.to_unit(AbsUnit::Mm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of centimeters.
|
||||
pub fn to_cm(self) -> f64 {
|
||||
self.to_unit(AbsUnit::Cm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of inches.
|
||||
pub fn to_inches(self) -> f64 {
|
||||
self.to_unit(AbsUnit::In)
|
||||
}
|
||||
|
||||
/// The absolute value of this length.
|
||||
pub fn abs(self) -> Self {
|
||||
Self::raw(self.to_raw().abs())
|
||||
}
|
||||
|
||||
/// The minimum of this and another absolute length.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self(self.0.min(other.0))
|
||||
}
|
||||
|
||||
/// Set to the minimum of this and another absolute length.
|
||||
pub fn set_min(&mut self, other: Self) {
|
||||
*self = (*self).min(other);
|
||||
}
|
||||
|
||||
/// The maximum of this and another absolute length.
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self(self.0.max(other.0))
|
||||
}
|
||||
|
||||
/// Set to the maximum of this and another absolute length.
|
||||
pub fn set_max(&mut self, other: Self) {
|
||||
*self = (*self).max(other);
|
||||
}
|
||||
|
||||
/// Whether the other absolute length fits into this one (i.e. is smaller).
|
||||
/// Allows for a bit of slack.
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.0 + 1e-6 >= other.0
|
||||
}
|
||||
|
||||
/// Compares two absolute lengths for whether they are approximately equal.
|
||||
pub fn approx_eq(self, other: Self) -> bool {
|
||||
self == other || (self - other).to_raw().abs() < 1e-6
|
||||
}
|
||||
|
||||
/// Perform a checked division by a number, returning zero if the result
|
||||
/// is not finite.
|
||||
pub fn safe_div(self, number: f64) -> Self {
|
||||
let result = self.to_raw() / number;
|
||||
if result.is_finite() {
|
||||
Self::raw(result)
|
||||
} else {
|
||||
Self::zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Abs {
|
||||
fn zero() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
|
||||
fn is_finite(self) -> bool {
|
||||
self.0.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Abs {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}pt", round_2(self.to_pt()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Abs {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self(-self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Abs {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Abs - Abs -> Abs);
|
||||
|
||||
impl Mul<f64> for Abs {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Abs> for f64 {
|
||||
type Output = Abs;
|
||||
|
||||
fn mul(self, other: Abs) -> Abs {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Abs {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self(self.0 / other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Abs {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, other: Self) -> f64 {
|
||||
self.to_raw() / other.to_raw()
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Abs += Abs);
|
||||
assign_impl!(Abs -= Abs);
|
||||
assign_impl!(Abs *= f64);
|
||||
assign_impl!(Abs /= f64);
|
||||
|
||||
impl Rem for Abs {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, other: Self) -> Self::Output {
|
||||
Self(self.0 % other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum for Abs {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
Self(iter.map(|s| s.0).sum())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum<&'a Self> for Abs {
|
||||
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
|
||||
Self(iter.map(|s| s.0).sum())
|
||||
}
|
||||
}
|
||||
|
||||
/// Different units of absolute measurement.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum AbsUnit {
|
||||
/// Points.
|
||||
Pt,
|
||||
/// Millimeters.
|
||||
Mm,
|
||||
/// Centimeters.
|
||||
Cm,
|
||||
/// Inches.
|
||||
In,
|
||||
}
|
||||
|
||||
impl AbsUnit {
|
||||
/// How many raw units correspond to a value of `1.0` in this unit.
|
||||
fn raw_scale(self) -> f64 {
|
||||
match self {
|
||||
AbsUnit::Pt => 1.0,
|
||||
AbsUnit::Mm => 2.83465,
|
||||
AbsUnit::Cm => 28.3465,
|
||||
AbsUnit::In => 72.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for AbsUnit {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
AbsUnit::Mm => "mm",
|
||||
AbsUnit::Pt => "pt",
|
||||
AbsUnit::Cm => "cm",
|
||||
AbsUnit::In => "in",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_length_unit_conversion() {
|
||||
assert!((Abs::mm(150.0).to_cm() - 15.0) < 1e-4);
|
||||
}
|
||||
}
|
@ -19,16 +19,16 @@ pub enum Align {
|
||||
|
||||
impl Align {
|
||||
/// Top-left alignment.
|
||||
pub const LEFT_TOP: Spec<Self> = Spec { x: Align::Left, y: Align::Top };
|
||||
pub const LEFT_TOP: Axes<Self> = Axes { x: Align::Left, y: Align::Top };
|
||||
|
||||
/// Center-horizon alignment.
|
||||
pub const CENTER_HORIZON: Spec<Self> = Spec { x: Align::Center, y: Align::Horizon };
|
||||
pub const CENTER_HORIZON: Axes<Self> = Axes { x: Align::Center, y: Align::Horizon };
|
||||
|
||||
/// The axis this alignment belongs to.
|
||||
pub const fn axis(self) -> SpecAxis {
|
||||
pub const fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::Left | Self::Center | Self::Right => SpecAxis::Horizontal,
|
||||
Self::Top | Self::Horizon | Self::Bottom => SpecAxis::Vertical,
|
||||
Self::Left | Self::Center | Self::Right => Axis::X,
|
||||
Self::Top | Self::Horizon | Self::Bottom => Axis::Y,
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,12 +44,13 @@ impl Align {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the position of this alignment in the given length.
|
||||
pub fn position(self, length: Length) -> Length {
|
||||
/// Returns the position of this alignment in a container with the given
|
||||
/// extentq.
|
||||
pub fn position(self, extent: Abs) -> Abs {
|
||||
match self {
|
||||
Self::Left | Self::Top => Length::zero(),
|
||||
Self::Center | Self::Horizon => length / 2.0,
|
||||
Self::Right | Self::Bottom => length,
|
||||
Self::Left | Self::Top => Abs::zero(),
|
||||
Self::Center | Self::Horizon => extent / 2.0,
|
||||
Self::Right | Self::Bottom => extent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
263
src/geom/axes.rs
Normal file
263
src/geom/axes.rs
Normal file
@ -0,0 +1,263 @@
|
||||
use std::any::Any;
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A container with a horizontal and vertical component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Axes<T> {
|
||||
/// The horizontal component.
|
||||
pub x: T,
|
||||
/// The vertical component.
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T> Axes<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub const fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Create a new instance with two equal components.
|
||||
pub fn splat(v: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self { x: v.clone(), y: v }
|
||||
}
|
||||
|
||||
/// Map the individual fields with `f`.
|
||||
pub fn map<F, U>(self, mut f: F) -> Axes<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Axes { x: f(self.x), y: f(self.y) }
|
||||
}
|
||||
|
||||
/// Convert from `&Axes<T>` to `Axes<&T>`.
|
||||
pub fn as_ref(&self) -> Axes<&T> {
|
||||
Axes { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&Axes<T>` to `Axes<&<T as Deref>::Target>`.
|
||||
pub fn as_deref(&self) -> Axes<&T::Target>
|
||||
where
|
||||
T: Deref,
|
||||
{
|
||||
Axes { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&mut Axes<T>` to `Axes<&mut T>`.
|
||||
pub fn as_mut(&mut self) -> Axes<&mut T> {
|
||||
Axes { x: &mut self.x, y: &mut self.y }
|
||||
}
|
||||
|
||||
/// Zip two instances into an instance over a tuple.
|
||||
pub fn zip<U>(self, other: Axes<U>) -> Axes<(T, U)> {
|
||||
Axes {
|
||||
x: (self.x, other.x),
|
||||
y: (self.y, other.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a condition is true for at least one of fields.
|
||||
pub fn any<F>(self, mut f: F) -> bool
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
f(&self.x) || f(&self.y)
|
||||
}
|
||||
|
||||
/// Whether a condition is true for both fields.
|
||||
pub fn all<F>(self, mut f: F) -> bool
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
f(&self.x) && f(&self.y)
|
||||
}
|
||||
|
||||
/// Filter the individual fields with a mask.
|
||||
pub fn filter(self, mask: Axes<bool>) -> Axes<Option<T>> {
|
||||
Axes {
|
||||
x: if mask.x { Some(self.x) } else { None },
|
||||
y: if mask.y { Some(self.y) } else { None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Axes<T> {
|
||||
/// Create a new instance with y set to its default value.
|
||||
pub fn with_x(x: T) -> Self {
|
||||
Self { x, y: T::default() }
|
||||
}
|
||||
|
||||
/// Create a new instance with x set to its default value.
|
||||
pub fn with_y(y: T) -> Self {
|
||||
Self { x: T::default(), y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> Axes<T> {
|
||||
/// The component-wise minimum of this and another instance.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.min(other.x),
|
||||
y: self.y.min(other.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// The component-wise minimum of this and another instance.
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.max(other.x),
|
||||
y: self.y.max(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<Axis> for Axes<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, axis: Axis) -> T {
|
||||
match axis {
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: Axis) -> &mut T {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Axes<T>
|
||||
where
|
||||
T: Debug + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if let Axes { x: Some(x), y: Some(y) } =
|
||||
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
|
||||
{
|
||||
write!(f, "{:?}-{:?}", x, y)
|
||||
} else if (&self.x as &dyn Any).is::<Abs>() {
|
||||
write!(f, "Size({:?}, {:?})", self.x, self.y)
|
||||
} else {
|
||||
write!(f, "Axes({:?}, {:?})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two layouting axes.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Axis {
|
||||
/// The horizontal axis.
|
||||
X,
|
||||
/// The vertical axis.
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
/// The direction with the given positivity for this axis.
|
||||
pub fn dir(self, positive: bool) -> Dir {
|
||||
match (self, positive) {
|
||||
(Self::X, true) => Dir::LTR,
|
||||
(Self::X, false) => Dir::RTL,
|
||||
(Self::Y, true) => Dir::TTB,
|
||||
(Self::Y, false) => Dir::BTT,
|
||||
}
|
||||
}
|
||||
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::X => Self::Y,
|
||||
Self::Y => Self::X,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Axis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::X => "horizontal",
|
||||
Self::Y => "vertical",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Axes<Option<T>> {
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Axes<T>) -> Axes<T> {
|
||||
Axes {
|
||||
x: self.x.unwrap_or(other.x),
|
||||
y: self.y.unwrap_or(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Axes<bool> {
|
||||
/// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`.
|
||||
pub fn select<T>(self, t: Axes<T>, f: Axes<T>) -> Axes<T> {
|
||||
Axes {
|
||||
x: if self.x { t.x } else { f.x },
|
||||
y: if self.y { t.y } else { f.y },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Axes<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self { x: !self.x, y: !self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Axes<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x | rhs.x, y: self.y | rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<bool> for Axes<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x | rhs, y: self.y | rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Axes<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x & rhs.x, y: self.y & rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<bool> for Axes<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x & rhs, y: self.y & rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for Axes<bool> {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.x |= rhs.x;
|
||||
self.y |= rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAndAssign for Axes<bool> {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.x &= rhs.x;
|
||||
self.y &= rhs.y;
|
||||
}
|
||||
}
|
@ -15,10 +15,10 @@ pub enum Dir {
|
||||
|
||||
impl Dir {
|
||||
/// The specific axis this direction belongs to.
|
||||
pub const fn axis(self) -> SpecAxis {
|
||||
pub const fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::LTR | Self::RTL => SpecAxis::Horizontal,
|
||||
Self::TTB | Self::BTT => SpecAxis::Vertical,
|
||||
Self::LTR | Self::RTL => Axis::X,
|
||||
Self::TTB | Self::BTT => Axis::Y,
|
||||
}
|
||||
}
|
||||
|
||||
|
26
src/geom/ellipse.rs
Normal file
26
src/geom/ellipse.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use super::*;
|
||||
|
||||
/// Produce a shape that approximates an axis-aligned ellipse.
|
||||
pub fn ellipse(size: Size, fill: Option<Paint>, stroke: Option<Stroke>) -> Shape {
|
||||
// https://stackoverflow.com/a/2007782
|
||||
let z = Abs::zero();
|
||||
let rx = size.x / 2.0;
|
||||
let ry = size.y / 2.0;
|
||||
let m = 0.551784;
|
||||
let mx = m * rx;
|
||||
let my = m * ry;
|
||||
let point = |x, y| Point::new(x + rx, y + ry);
|
||||
|
||||
let mut path = Path::new();
|
||||
path.move_to(point(-rx, z));
|
||||
path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
|
||||
path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
|
||||
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
|
||||
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
|
||||
|
||||
Shape {
|
||||
geometry: Geometry::Path(path),
|
||||
stroke,
|
||||
fill,
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ use super::*;
|
||||
pub struct Em(Scalar);
|
||||
|
||||
impl Em {
|
||||
/// The zero length.
|
||||
/// The zero em length.
|
||||
pub const fn zero() -> Self {
|
||||
Self(Scalar(0.0))
|
||||
}
|
||||
@ -28,7 +28,7 @@ impl Em {
|
||||
}
|
||||
|
||||
/// Create an em length from a length at the given font size.
|
||||
pub fn from_length(length: Length, font_size: Length) -> Self {
|
||||
pub fn from_length(length: Abs, font_size: Abs) -> Self {
|
||||
let result = length / font_size;
|
||||
if result.is_finite() {
|
||||
Self(Scalar(result))
|
||||
@ -42,10 +42,10 @@ impl Em {
|
||||
(self.0).0
|
||||
}
|
||||
|
||||
/// Convert to a length at the given font size.
|
||||
pub fn at(self, font_size: Length) -> Length {
|
||||
/// Convert to an absolute length at the given font size.
|
||||
pub fn at(self, font_size: Abs) -> Abs {
|
||||
let resolved = font_size * self.get();
|
||||
if resolved.is_finite() { resolved } else { Length::zero() }
|
||||
if resolved.is_finite() { resolved } else { Abs::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@ use super::*;
|
||||
|
||||
/// A fraction of remaining space.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Fraction(Scalar);
|
||||
pub struct Fr(Scalar);
|
||||
|
||||
impl Fraction {
|
||||
impl Fr {
|
||||
/// Takes up zero space: `0fr`.
|
||||
pub const fn zero() -> Self {
|
||||
Self(Scalar(0.0))
|
||||
@ -31,17 +31,17 @@ impl Fraction {
|
||||
}
|
||||
|
||||
/// Determine this fraction's share in the remaining space.
|
||||
pub fn share(self, total: Self, remaining: Length) -> Length {
|
||||
pub fn share(self, total: Self, remaining: Abs) -> Abs {
|
||||
let ratio = self / total;
|
||||
if ratio.is_finite() && remaining.is_finite() {
|
||||
ratio * remaining
|
||||
} else {
|
||||
Length::zero()
|
||||
Abs::zero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Fraction {
|
||||
impl Numeric for Fr {
|
||||
fn zero() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
@ -51,13 +51,13 @@ impl Numeric for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Fraction {
|
||||
impl Debug for Fr {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}fr", round_2(self.get()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Fraction {
|
||||
impl Neg for Fr {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
@ -65,7 +65,7 @@ impl Neg for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Fraction {
|
||||
impl Add for Fr {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
@ -73,9 +73,9 @@ impl Add for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Fraction - Fraction -> Fraction);
|
||||
sub_impl!(Fr - Fr -> Fr);
|
||||
|
||||
impl Mul<f64> for Fraction {
|
||||
impl Mul<f64> for Fr {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
@ -83,15 +83,15 @@ impl Mul<f64> for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Fraction> for f64 {
|
||||
type Output = Fraction;
|
||||
impl Mul<Fr> for f64 {
|
||||
type Output = Fr;
|
||||
|
||||
fn mul(self, other: Fraction) -> Fraction {
|
||||
fn mul(self, other: Fr) -> Fr {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Fraction {
|
||||
impl Div<f64> for Fr {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
@ -99,7 +99,7 @@ impl Div<f64> for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Fraction {
|
||||
impl Div for Fr {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, other: Self) -> f64 {
|
||||
@ -107,12 +107,12 @@ impl Div for Fraction {
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Fraction += Fraction);
|
||||
assign_impl!(Fraction -= Fraction);
|
||||
assign_impl!(Fraction *= f64);
|
||||
assign_impl!(Fraction /= f64);
|
||||
assign_impl!(Fr += Fr);
|
||||
assign_impl!(Fr -= Fr);
|
||||
assign_impl!(Fr *= f64);
|
||||
assign_impl!(Fr /= f64);
|
||||
|
||||
impl Sum for Fraction {
|
||||
impl Sum for Fr {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
Self(iter.map(|s| s.0).sum())
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// A container with a main and cross component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Gen<T> {
|
||||
/// The main component.
|
||||
pub cross: T,
|
||||
/// The cross component.
|
||||
pub main: T,
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub const fn new(cross: T, main: T) -> Self {
|
||||
Self { cross, main }
|
||||
}
|
||||
|
||||
/// Create a new instance with two equal components.
|
||||
pub fn splat(value: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self { cross: value.clone(), main: value }
|
||||
}
|
||||
|
||||
/// Maps the individual fields with `f`.
|
||||
pub fn map<F, U>(self, mut f: F) -> Gen<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Gen { cross: f(self.cross), main: f(self.main) }
|
||||
}
|
||||
|
||||
/// Convert to the specific representation, given the current main axis.
|
||||
pub fn to_spec(self, main: SpecAxis) -> Spec<T> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Spec::new(self.main, self.cross),
|
||||
SpecAxis::Vertical => Spec::new(self.cross, self.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen<Length> {
|
||||
/// The zero value.
|
||||
pub fn zero() -> Self {
|
||||
Self {
|
||||
cross: Length::zero(),
|
||||
main: Length::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self, main: SpecAxis) -> Point {
|
||||
self.to_spec(main).to_point()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<GenAxis> for Gen<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, axis: GenAxis) -> T {
|
||||
match axis {
|
||||
GenAxis::Cross => self.cross,
|
||||
GenAxis::Main => self.main,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: GenAxis) -> &mut T {
|
||||
match axis {
|
||||
GenAxis::Cross => &mut self.cross,
|
||||
GenAxis::Main => &mut self.main,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Gen<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "Gen({:?}, {:?})", self.cross, self.main)
|
||||
}
|
||||
}
|
||||
|
||||
/// Two generic axes of a container.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum GenAxis {
|
||||
/// The minor / inline axis.
|
||||
Cross,
|
||||
/// The major / block axis.
|
||||
Main,
|
||||
}
|
||||
|
||||
impl GenAxis {
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Cross => Self::Main,
|
||||
Self::Main => Self::Cross,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +1,41 @@
|
||||
use super::*;
|
||||
|
||||
/// An absolute length.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Length(Scalar);
|
||||
/// A length, possibly expressed with contextual units.
|
||||
///
|
||||
/// Currently supports absolute and font-relative units, but support could quite
|
||||
/// easily be extended to other units.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Length {
|
||||
/// The absolute part.
|
||||
pub abs: Abs,
|
||||
/// The font-relative part.
|
||||
pub em: Em,
|
||||
}
|
||||
|
||||
impl Length {
|
||||
/// The zero length.
|
||||
pub const fn zero() -> Self {
|
||||
Self(Scalar(0.0))
|
||||
Self { abs: Abs::zero(), em: Em::zero() }
|
||||
}
|
||||
|
||||
/// The infinite length.
|
||||
pub const fn inf() -> Self {
|
||||
Self(Scalar(f64::INFINITY))
|
||||
}
|
||||
|
||||
/// Create a length from a number of raw units.
|
||||
pub const fn raw(raw: f64) -> Self {
|
||||
Self(Scalar(raw))
|
||||
}
|
||||
|
||||
/// Create a length from a value in a unit.
|
||||
pub fn with_unit(val: f64, unit: LengthUnit) -> Self {
|
||||
Self(Scalar(val * unit.raw_scale()))
|
||||
}
|
||||
|
||||
/// Create a length from a number of points.
|
||||
pub fn pt(pt: f64) -> Self {
|
||||
Self::with_unit(pt, LengthUnit::Pt)
|
||||
}
|
||||
|
||||
/// Create a length from a number of millimeters.
|
||||
pub fn mm(mm: f64) -> Self {
|
||||
Self::with_unit(mm, LengthUnit::Mm)
|
||||
}
|
||||
|
||||
/// Create a length from a number of centimeters.
|
||||
pub fn cm(cm: f64) -> Self {
|
||||
Self::with_unit(cm, LengthUnit::Cm)
|
||||
}
|
||||
|
||||
/// Create a length from a number of inches.
|
||||
pub fn inches(inches: f64) -> Self {
|
||||
Self::with_unit(inches, LengthUnit::In)
|
||||
}
|
||||
|
||||
/// Get the value of this length in raw units.
|
||||
pub const fn to_raw(self) -> f64 {
|
||||
(self.0).0
|
||||
}
|
||||
|
||||
/// Get the value of this length in a unit.
|
||||
pub fn to_unit(self, unit: LengthUnit) -> f64 {
|
||||
self.to_raw() / unit.raw_scale()
|
||||
}
|
||||
|
||||
/// Convert this to a number of points.
|
||||
pub fn to_pt(self) -> f64 {
|
||||
self.to_unit(LengthUnit::Pt)
|
||||
}
|
||||
|
||||
/// Convert this to a number of millimeters.
|
||||
pub fn to_mm(self) -> f64 {
|
||||
self.to_unit(LengthUnit::Mm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of centimeters.
|
||||
pub fn to_cm(self) -> f64 {
|
||||
self.to_unit(LengthUnit::Cm)
|
||||
}
|
||||
|
||||
/// Convert this to a number of inches.
|
||||
pub fn to_inches(self) -> f64 {
|
||||
self.to_unit(LengthUnit::In)
|
||||
}
|
||||
|
||||
/// The absolute value of this length.
|
||||
pub fn abs(self) -> Self {
|
||||
Self::raw(self.to_raw().abs())
|
||||
}
|
||||
|
||||
/// The minimum of this and another length.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self(self.0.min(other.0))
|
||||
}
|
||||
|
||||
/// Set to the minimum of this and another length.
|
||||
pub fn set_min(&mut self, other: Self) {
|
||||
*self = (*self).min(other);
|
||||
}
|
||||
|
||||
/// The maximum of this and another length.
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self(self.0.max(other.0))
|
||||
}
|
||||
|
||||
/// Set to the maximum of this and another length.
|
||||
pub fn set_max(&mut self, other: Self) {
|
||||
*self = (*self).max(other);
|
||||
}
|
||||
|
||||
/// Whether the other length fits into this one (i.e. is smaller). Allows
|
||||
/// for a bit of slack.
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.0 + 1e-6 >= other.0
|
||||
}
|
||||
|
||||
/// Compares two lengths for whether they are approximately equal.
|
||||
pub fn approx_eq(self, other: Self) -> bool {
|
||||
self == other || (self - other).to_raw().abs() < 1e-6
|
||||
}
|
||||
|
||||
/// Perform a checked division by a number, returning zero if the result
|
||||
/// is not finite.
|
||||
pub fn safe_div(self, number: f64) -> Self {
|
||||
let result = self.to_raw() / number;
|
||||
if result.is_finite() {
|
||||
Self::raw(result)
|
||||
/// Try to divide two lengths.
|
||||
pub fn try_div(self, other: Self) -> Option<f64> {
|
||||
if self.abs.is_zero() && other.abs.is_zero() {
|
||||
Some(self.em / other.em)
|
||||
} else if self.em.is_zero() && other.em.is_zero() {
|
||||
Some(self.abs / other.abs)
|
||||
} else {
|
||||
Self::zero()
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Length {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match (self.abs.is_zero(), self.em.is_zero()) {
|
||||
(false, false) => write!(f, "{:?} + {:?}", self.abs, self.em),
|
||||
(true, false) => self.em.fmt(f),
|
||||
(_, true) => self.abs.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,29 +46,56 @@ impl Numeric for Length {
|
||||
}
|
||||
|
||||
fn is_finite(self) -> bool {
|
||||
self.0.is_finite()
|
||||
self.abs.is_finite() && self.em.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Length {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}pt", round_2(self.to_pt()))
|
||||
impl PartialOrd for Length {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
if self.em.is_zero() && other.em.is_zero() {
|
||||
self.abs.partial_cmp(&other.abs)
|
||||
} else if self.abs.is_zero() && other.abs.is_zero() {
|
||||
self.em.partial_cmp(&other.em)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Abs> for Length {
|
||||
fn from(abs: Abs) -> Self {
|
||||
Self { abs, em: Em::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Em> for Length {
|
||||
fn from(em: Em) -> Self {
|
||||
Self { abs: Abs::zero(), em }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Abs> for Rel<Length> {
|
||||
fn from(abs: Abs) -> Self {
|
||||
Rel::from(Length::from(abs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self(-self.0)
|
||||
fn neg(self) -> Self::Output {
|
||||
Self { abs: -self.abs, em: -self.em }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
abs: self.abs + rhs.abs,
|
||||
em: self.em + rhs.em,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,32 +104,16 @@ sub_impl!(Length - Length -> Length);
|
||||
impl Mul<f64> for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self(self.0 * other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Length> for f64 {
|
||||
type Output = Length;
|
||||
|
||||
fn mul(self, other: Length) -> Length {
|
||||
other * self
|
||||
fn mul(self, rhs: f64) -> Self::Output {
|
||||
Self { abs: self.abs * rhs, em: self.em * rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self(self.0 / other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Length {
|
||||
type Output = f64;
|
||||
|
||||
fn div(self, other: Self) -> f64 {
|
||||
self.to_raw() / other.to_raw()
|
||||
fn div(self, rhs: f64) -> Self::Output {
|
||||
Self { abs: self.abs / rhs, em: self.em / rhs }
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,69 +121,3 @@ assign_impl!(Length += Length);
|
||||
assign_impl!(Length -= Length);
|
||||
assign_impl!(Length *= f64);
|
||||
assign_impl!(Length /= f64);
|
||||
|
||||
impl Rem for Length {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, other: Self) -> Self::Output {
|
||||
Self(self.0 % other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum for Length {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
Self(iter.map(|s| s.0).sum())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sum<&'a Self> for Length {
|
||||
fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
|
||||
Self(iter.map(|s| s.0).sum())
|
||||
}
|
||||
}
|
||||
|
||||
/// Different units of length measurement.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum LengthUnit {
|
||||
/// Points.
|
||||
Pt,
|
||||
/// Millimeters.
|
||||
Mm,
|
||||
/// Centimeters.
|
||||
Cm,
|
||||
/// Inches.
|
||||
In,
|
||||
}
|
||||
|
||||
impl LengthUnit {
|
||||
/// How many raw units correspond to a value of `1.0` in this unit.
|
||||
fn raw_scale(self) -> f64 {
|
||||
match self {
|
||||
LengthUnit::Pt => 1.0,
|
||||
LengthUnit::Mm => 2.83465,
|
||||
LengthUnit::Cm => 28.3465,
|
||||
LengthUnit::In => 72.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LengthUnit {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
LengthUnit::Mm => "mm",
|
||||
LengthUnit::Pt => "pt",
|
||||
LengthUnit::Cm => "cm",
|
||||
LengthUnit::In => "in",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_length_unit_conversion() {
|
||||
assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4);
|
||||
}
|
||||
}
|
||||
|
@ -2,42 +2,46 @@
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
mod abs;
|
||||
mod align;
|
||||
mod angle;
|
||||
mod axes;
|
||||
mod corners;
|
||||
mod dir;
|
||||
mod ellipse;
|
||||
mod em;
|
||||
mod fraction;
|
||||
mod gen;
|
||||
mod fr;
|
||||
mod length;
|
||||
mod paint;
|
||||
mod path;
|
||||
mod point;
|
||||
mod ratio;
|
||||
mod rect;
|
||||
mod relative;
|
||||
mod rel;
|
||||
mod rounded;
|
||||
mod scalar;
|
||||
mod sides;
|
||||
mod spec;
|
||||
mod size;
|
||||
mod transform;
|
||||
|
||||
pub use abs::*;
|
||||
pub use align::*;
|
||||
pub use angle::*;
|
||||
pub use axes::*;
|
||||
pub use corners::*;
|
||||
pub use dir::*;
|
||||
pub use ellipse::*;
|
||||
pub use em::*;
|
||||
pub use fraction::*;
|
||||
pub use gen::*;
|
||||
pub use fr::*;
|
||||
pub use length::*;
|
||||
pub use paint::*;
|
||||
pub use path::*;
|
||||
pub use point::*;
|
||||
pub use ratio::*;
|
||||
pub use rect::*;
|
||||
pub use relative::*;
|
||||
pub use rel::*;
|
||||
pub use rounded::*;
|
||||
pub use scalar::*;
|
||||
pub use sides::*;
|
||||
pub use spec::*;
|
||||
pub use size::*;
|
||||
pub use transform::*;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
@ -82,8 +86,6 @@ pub enum Geometry {
|
||||
Line(Point),
|
||||
/// A rectangle with its origin in the topleft corner.
|
||||
Rect(Size),
|
||||
/// A ellipse with its origin in the topleft corner.
|
||||
Ellipse(Size),
|
||||
/// A bezier path.
|
||||
Path(Path),
|
||||
}
|
||||
|
@ -394,14 +394,14 @@ pub struct Stroke {
|
||||
/// The stroke's paint.
|
||||
pub paint: Paint,
|
||||
/// The stroke's thickness.
|
||||
pub thickness: Length,
|
||||
pub thickness: Abs,
|
||||
}
|
||||
|
||||
impl Default for Stroke {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paint: Paint::Solid(Color::BLACK.into()),
|
||||
thickness: Length::pt(1.0),
|
||||
thickness: Abs::pt(1.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ impl Path {
|
||||
|
||||
/// Create a path that describes a rectangle.
|
||||
pub fn rect(size: Size) -> Self {
|
||||
let z = Length::zero();
|
||||
let z = Abs::zero();
|
||||
let point = Point::new;
|
||||
let mut path = Self::new();
|
||||
path.move_to(point(z, z));
|
||||
@ -32,25 +32,6 @@ impl Path {
|
||||
path
|
||||
}
|
||||
|
||||
/// Create a path that approximates an axis-aligned ellipse.
|
||||
pub fn ellipse(size: Size) -> Self {
|
||||
// https://stackoverflow.com/a/2007782
|
||||
let z = Length::zero();
|
||||
let rx = size.x / 2.0;
|
||||
let ry = size.y / 2.0;
|
||||
let m = 0.551784;
|
||||
let mx = m * rx;
|
||||
let my = m * ry;
|
||||
let point = |x, y| Point::new(x + rx, y + ry);
|
||||
let mut path = Self::new();
|
||||
path.move_to(point(-rx, z));
|
||||
path.cubic_to(point(-rx, -my), point(-mx, -ry), point(z, -ry));
|
||||
path.cubic_to(point(mx, -ry), point(rx, -my), point(rx, z));
|
||||
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
|
||||
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
|
||||
path
|
||||
}
|
||||
|
||||
/// Push a [`MoveTo`](PathElement::MoveTo) element.
|
||||
pub fn move_to(&mut self, p: Point) {
|
||||
self.0.push(PathElement::MoveTo(p));
|
||||
@ -71,22 +52,3 @@ impl Path {
|
||||
self.0.push(PathElement::ClosePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the control points for a bezier curve that describes a circular arc for
|
||||
/// a start point, an end point and a center of the circle whose arc connects
|
||||
/// the two.
|
||||
pub fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] {
|
||||
// https://stackoverflow.com/a/44829356/1567835
|
||||
let a = start - center;
|
||||
let b = end - center;
|
||||
|
||||
let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
|
||||
let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
|
||||
let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
|
||||
/ (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
|
||||
|
||||
let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
|
||||
let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
|
||||
|
||||
[start, control_1, control_2, end]
|
||||
}
|
||||
|
@ -4,35 +4,35 @@ use super::*;
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Point {
|
||||
/// The x coordinate.
|
||||
pub x: Length,
|
||||
pub x: Abs,
|
||||
/// The y coordinate.
|
||||
pub y: Length,
|
||||
pub y: Abs,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
/// The origin point.
|
||||
pub const fn zero() -> Self {
|
||||
Self { x: Length::zero(), y: Length::zero() }
|
||||
Self { x: Abs::zero(), y: Abs::zero() }
|
||||
}
|
||||
|
||||
/// Create a new point from x and y coordinates.
|
||||
pub const fn new(x: Length, y: Length) -> Self {
|
||||
pub const fn new(x: Abs, y: Abs) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Create an instance with two equal components.
|
||||
pub const fn splat(value: Length) -> Self {
|
||||
pub const fn splat(value: Abs) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
|
||||
/// Create a new point with y set to zero.
|
||||
pub const fn with_x(x: Length) -> Self {
|
||||
Self { x, y: Length::zero() }
|
||||
pub const fn with_x(x: Abs) -> Self {
|
||||
Self { x, y: Abs::zero() }
|
||||
}
|
||||
|
||||
/// Create a new point with x set to zero.
|
||||
pub const fn with_y(y: Length) -> Self {
|
||||
Self { x: Length::zero(), y }
|
||||
pub const fn with_y(y: Abs) -> Self {
|
||||
Self { x: Abs::zero(), y }
|
||||
}
|
||||
|
||||
/// Transform the point with the given transformation.
|
||||
@ -54,20 +54,20 @@ impl Numeric for Point {
|
||||
}
|
||||
}
|
||||
|
||||
impl Get<SpecAxis> for Point {
|
||||
type Component = Length;
|
||||
impl Get<Axis> for Point {
|
||||
type Component = Abs;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> Length {
|
||||
fn get(self, axis: Axis) -> Abs {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.x,
|
||||
SpecAxis::Vertical => self.y,
|
||||
Axis::X => self.x,
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut Length {
|
||||
fn get_mut(&mut self, axis: Axis) -> &mut Abs {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.x,
|
||||
SpecAxis::Vertical => &mut self.y,
|
||||
Axis::X => &mut self.x,
|
||||
Axis::Y => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
184
src/geom/rect.rs
184
src/geom/rect.rs
@ -1,184 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
use std::mem;
|
||||
|
||||
/// A rectangle with rounded corners.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct RoundedRect {
|
||||
/// The size of the rectangle.
|
||||
pub size: Size,
|
||||
/// The radius at each corner.
|
||||
pub radius: Corners<Length>,
|
||||
}
|
||||
|
||||
impl RoundedRect {
|
||||
/// Create a new rounded rectangle.
|
||||
pub fn new(size: Size, radius: Corners<Length>) -> Self {
|
||||
Self { size, radius }
|
||||
}
|
||||
|
||||
/// Output all constituent shapes of the rectangle in order. The last one is
|
||||
/// in the foreground. The function will output multiple items if the stroke
|
||||
/// properties differ by side.
|
||||
pub fn shapes(
|
||||
self,
|
||||
fill: Option<Paint>,
|
||||
stroke: Sides<Option<Stroke>>,
|
||||
) -> Vec<Shape> {
|
||||
let mut res = vec![];
|
||||
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
||||
res.push(Shape {
|
||||
geometry: self.fill_geometry(),
|
||||
fill,
|
||||
stroke: if stroke.is_uniform() { stroke.top } else { None },
|
||||
});
|
||||
}
|
||||
|
||||
if !stroke.is_uniform() {
|
||||
for (path, stroke) in self.stroke_segments(stroke) {
|
||||
if stroke.is_some() {
|
||||
res.push(Shape {
|
||||
geometry: Geometry::Path(path),
|
||||
fill: None,
|
||||
stroke,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Output the shape of the rectangle as a path or primitive rectangle,
|
||||
/// depending on whether it is rounded.
|
||||
fn fill_geometry(self) -> Geometry {
|
||||
if self.radius.iter().copied().all(Length::is_zero) {
|
||||
Geometry::Rect(self.size)
|
||||
} else {
|
||||
let mut paths = self.stroke_segments(Sides::splat(None));
|
||||
assert_eq!(paths.len(), 1);
|
||||
Geometry::Path(paths.pop().unwrap().0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Output the minimum number of paths along the rectangles border.
|
||||
fn stroke_segments(
|
||||
self,
|
||||
strokes: Sides<Option<Stroke>>,
|
||||
) -> Vec<(Path, Option<Stroke>)> {
|
||||
let mut res = vec![];
|
||||
|
||||
let mut connection = Connection::default();
|
||||
let mut path = Path::new();
|
||||
let mut always_continuous = true;
|
||||
|
||||
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
|
||||
let continuous = strokes.get(side) == strokes.get(side.next_cw());
|
||||
connection = connection.advance(continuous && side != Side::Left);
|
||||
always_continuous &= continuous;
|
||||
|
||||
draw_side(
|
||||
&mut path,
|
||||
side,
|
||||
self.size,
|
||||
self.radius.get(side.start_corner()),
|
||||
self.radius.get(side.end_corner()),
|
||||
connection,
|
||||
);
|
||||
|
||||
if !continuous {
|
||||
res.push((mem::take(&mut path), strokes.get(side)));
|
||||
}
|
||||
}
|
||||
|
||||
if always_continuous {
|
||||
path.close_path();
|
||||
}
|
||||
|
||||
if !path.0.is_empty() {
|
||||
res.push((path, strokes.left));
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
|
||||
/// right arc will be drawn halfway if and only if there is no connection.
|
||||
fn draw_side(
|
||||
path: &mut Path,
|
||||
side: Side,
|
||||
size: Size,
|
||||
start_radius: Length,
|
||||
end_radius: Length,
|
||||
connection: Connection,
|
||||
) {
|
||||
let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 });
|
||||
let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 });
|
||||
let length = size.get(side.axis());
|
||||
|
||||
// The arcs for a border of the rectangle along the x-axis, starting at (0,0).
|
||||
let p1 = Point::with_x(start_radius);
|
||||
let mut arc1 = bezier_arc(
|
||||
p1 + Point::new(
|
||||
-angle_left.sin() * start_radius,
|
||||
(1.0 - angle_left.cos()) * start_radius,
|
||||
),
|
||||
Point::new(start_radius, start_radius),
|
||||
p1,
|
||||
);
|
||||
|
||||
let p2 = Point::with_x(length - end_radius);
|
||||
let mut arc2 = bezier_arc(
|
||||
p2,
|
||||
Point::new(length - end_radius, end_radius),
|
||||
p2 + Point::new(
|
||||
angle_right.sin() * end_radius,
|
||||
(1.0 - angle_right.cos()) * end_radius,
|
||||
),
|
||||
);
|
||||
|
||||
let transform = match side {
|
||||
Side::Left => Transform::rotate(Angle::deg(-90.0))
|
||||
.post_concat(Transform::translate(Length::zero(), size.y)),
|
||||
Side::Bottom => Transform::rotate(Angle::deg(180.0))
|
||||
.post_concat(Transform::translate(size.x, size.y)),
|
||||
Side::Right => Transform::rotate(Angle::deg(90.0))
|
||||
.post_concat(Transform::translate(size.x, Length::zero())),
|
||||
_ => Transform::identity(),
|
||||
};
|
||||
|
||||
arc1 = arc1.map(|x| x.transform(transform));
|
||||
arc2 = arc2.map(|x| x.transform(transform));
|
||||
|
||||
if !connection.prev {
|
||||
path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] });
|
||||
}
|
||||
|
||||
if !start_radius.is_zero() {
|
||||
path.cubic_to(arc1[1], arc1[2], arc1[3]);
|
||||
}
|
||||
|
||||
path.line_to(arc2[0]);
|
||||
|
||||
if !connection.next && !end_radius.is_zero() {
|
||||
path.cubic_to(arc2[1], arc2[2], arc2[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Indicates which sides of the border strokes in a 2D polygon are connected to
|
||||
/// their neighboring sides.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
struct Connection {
|
||||
prev: bool,
|
||||
next: bool,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Advance to the next clockwise side of the polygon. The argument
|
||||
/// indicates whether the border is connected on the right side of the next
|
||||
/// edge.
|
||||
pub fn advance(self, next: bool) -> Self {
|
||||
Self { prev: self.next, next }
|
||||
}
|
||||
}
|
@ -2,14 +2,14 @@ use super::*;
|
||||
|
||||
/// A value that is composed of a relative and an absolute part.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Relative<T: Numeric> {
|
||||
pub struct Rel<T: Numeric> {
|
||||
/// The relative part.
|
||||
pub rel: Ratio,
|
||||
/// The absolute part.
|
||||
pub abs: T,
|
||||
}
|
||||
|
||||
impl<T: Numeric> Relative<T> {
|
||||
impl<T: Numeric> Rel<T> {
|
||||
/// The zero relative.
|
||||
pub fn zero() -> Self {
|
||||
Self { rel: Ratio::zero(), abs: T::zero() }
|
||||
@ -41,16 +41,29 @@ impl<T: Numeric> Relative<T> {
|
||||
}
|
||||
|
||||
/// Map the absolute part with `f`.
|
||||
pub fn map<F, U>(self, f: F) -> Relative<U>
|
||||
pub fn map<F, U>(self, f: F) -> Rel<U>
|
||||
where
|
||||
F: FnOnce(T) -> U,
|
||||
U: Numeric,
|
||||
{
|
||||
Relative { rel: self.rel, abs: f(self.abs) }
|
||||
Rel { rel: self.rel, abs: f(self.abs) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Debug for Relative<T> {
|
||||
impl Rel<Length> {
|
||||
/// Try to divide two relative lengths.
|
||||
pub fn try_div(self, other: Self) -> Option<f64> {
|
||||
if self.rel.is_zero() && other.rel.is_zero() {
|
||||
self.abs.try_div(other.abs)
|
||||
} else if self.abs.is_zero() && other.abs.is_zero() {
|
||||
Some(self.rel / other.rel)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Debug for Rel<T> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match (self.rel.is_zero(), self.abs.is_zero()) {
|
||||
(false, false) => write!(f, "{:?} + {:?}", self.rel, self.abs),
|
||||
@ -60,19 +73,19 @@ impl<T: Numeric> Debug for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> From<T> for Relative<T> {
|
||||
impl<T: Numeric> From<T> for Rel<T> {
|
||||
fn from(abs: T) -> Self {
|
||||
Self { rel: Ratio::zero(), abs }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> From<Ratio> for Relative<T> {
|
||||
impl<T: Numeric> From<Ratio> for Rel<T> {
|
||||
fn from(rel: Ratio) -> Self {
|
||||
Self { rel, abs: T::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> {
|
||||
impl<T: Numeric + PartialOrd> PartialOrd for Rel<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
if self.rel.is_zero() && other.rel.is_zero() {
|
||||
self.abs.partial_cmp(&other.abs)
|
||||
@ -84,7 +97,7 @@ impl<T: Numeric + PartialOrd> PartialOrd for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Neg for Relative<T> {
|
||||
impl<T: Numeric> Neg for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
@ -92,7 +105,7 @@ impl<T: Numeric> Neg for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Add for Relative<T> {
|
||||
impl<T: Numeric> Add for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self::Output {
|
||||
@ -103,7 +116,7 @@ impl<T: Numeric> Add for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Sub for Relative<T> {
|
||||
impl<T: Numeric> Sub for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
@ -111,7 +124,7 @@ impl<T: Numeric> Sub for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Mul<f64> for Relative<T> {
|
||||
impl<T: Numeric> Mul<f64> for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self::Output {
|
||||
@ -122,15 +135,15 @@ impl<T: Numeric> Mul<f64> for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Mul<Relative<T>> for f64 {
|
||||
type Output = Relative<T>;
|
||||
impl<T: Numeric> Mul<Rel<T>> for f64 {
|
||||
type Output = Rel<T>;
|
||||
|
||||
fn mul(self, other: Relative<T>) -> Self::Output {
|
||||
fn mul(self, other: Rel<T>) -> Self::Output {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Div<f64> for Relative<T> {
|
||||
impl<T: Numeric> Div<f64> for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self::Output {
|
||||
@ -141,28 +154,28 @@ impl<T: Numeric> Div<f64> for Relative<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric + AddAssign> AddAssign for Relative<T> {
|
||||
impl<T: Numeric + AddAssign> AddAssign for Rel<T> {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.rel += other.rel;
|
||||
self.abs += other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric + SubAssign> SubAssign for Relative<T> {
|
||||
impl<T: Numeric + SubAssign> SubAssign for Rel<T> {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
self.rel -= other.rel;
|
||||
self.abs -= other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Relative<T> {
|
||||
impl<T: Numeric + MulAssign<f64>> MulAssign<f64> for Rel<T> {
|
||||
fn mul_assign(&mut self, other: f64) {
|
||||
self.rel *= other;
|
||||
self.abs *= other;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> {
|
||||
impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Rel<T> {
|
||||
fn div_assign(&mut self, other: f64) {
|
||||
self.rel /= other;
|
||||
self.abs /= other;
|
||||
@ -170,25 +183,25 @@ impl<T: Numeric + DivAssign<f64>> DivAssign<f64> for Relative<T> {
|
||||
}
|
||||
|
||||
impl<T: Numeric> Add<T> for Ratio {
|
||||
type Output = Relative<T>;
|
||||
type Output = Rel<T>;
|
||||
|
||||
fn add(self, other: T) -> Self::Output {
|
||||
Relative::from(self) + Relative::from(other)
|
||||
Rel::from(self) + Rel::from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Add<T> for Relative<T> {
|
||||
impl<T: Numeric> Add<T> for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: T) -> Self::Output {
|
||||
self + Relative::from(other)
|
||||
self + Rel::from(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Numeric> Add<Ratio> for Relative<T> {
|
||||
impl<T: Numeric> Add<Ratio> for Rel<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Ratio) -> Self::Output {
|
||||
self + Relative::from(other)
|
||||
self + Rel::from(other)
|
||||
}
|
||||
}
|
187
src/geom/rounded.rs
Normal file
187
src/geom/rounded.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use super::*;
|
||||
|
||||
use std::mem;
|
||||
|
||||
/// Produce shapes that together make up a rounded rectangle.
|
||||
pub fn rounded_rect(
|
||||
size: Size,
|
||||
radius: Corners<Abs>,
|
||||
fill: Option<Paint>,
|
||||
stroke: Sides<Option<Stroke>>,
|
||||
) -> Vec<Shape> {
|
||||
let mut res = vec![];
|
||||
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
||||
res.push(Shape {
|
||||
geometry: fill_geometry(size, radius),
|
||||
fill,
|
||||
stroke: if stroke.is_uniform() { stroke.top } else { None },
|
||||
});
|
||||
}
|
||||
|
||||
if !stroke.is_uniform() {
|
||||
for (path, stroke) in stroke_segments(size, radius, stroke) {
|
||||
if stroke.is_some() {
|
||||
res.push(Shape {
|
||||
geometry: Geometry::Path(path),
|
||||
fill: None,
|
||||
stroke,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Output the shape of the rectangle as a path or primitive rectangle,
|
||||
/// depending on whether it is rounded.
|
||||
fn fill_geometry(size: Size, radius: Corners<Abs>) -> Geometry {
|
||||
if radius.iter().copied().all(Abs::is_zero) {
|
||||
Geometry::Rect(size)
|
||||
} else {
|
||||
let mut paths = stroke_segments(size, radius, Sides::splat(None));
|
||||
assert_eq!(paths.len(), 1);
|
||||
Geometry::Path(paths.pop().unwrap().0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Output the minimum number of paths along the rectangles border.
|
||||
fn stroke_segments(
|
||||
size: Size,
|
||||
radius: Corners<Abs>,
|
||||
stroke: Sides<Option<Stroke>>,
|
||||
) -> Vec<(Path, Option<Stroke>)> {
|
||||
let mut res = vec![];
|
||||
|
||||
let mut connection = Connection::default();
|
||||
let mut path = Path::new();
|
||||
let mut always_continuous = true;
|
||||
|
||||
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
|
||||
let continuous = stroke.get(side) == stroke.get(side.next_cw());
|
||||
connection = connection.advance(continuous && side != Side::Left);
|
||||
always_continuous &= continuous;
|
||||
|
||||
draw_side(
|
||||
&mut path,
|
||||
side,
|
||||
size,
|
||||
radius.get(side.start_corner()),
|
||||
radius.get(side.end_corner()),
|
||||
connection,
|
||||
);
|
||||
|
||||
if !continuous {
|
||||
res.push((mem::take(&mut path), stroke.get(side)));
|
||||
}
|
||||
}
|
||||
|
||||
if always_continuous {
|
||||
path.close_path();
|
||||
}
|
||||
|
||||
if !path.0.is_empty() {
|
||||
res.push((path, stroke.left));
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
|
||||
/// right arc will be drawn halfway if and only if there is no connection.
|
||||
fn draw_side(
|
||||
path: &mut Path,
|
||||
side: Side,
|
||||
size: Size,
|
||||
start_radius: Abs,
|
||||
end_radius: Abs,
|
||||
connection: Connection,
|
||||
) {
|
||||
let angle_left = Angle::deg(if connection.prev { 90.0 } else { 45.0 });
|
||||
let angle_right = Angle::deg(if connection.next { 90.0 } else { 45.0 });
|
||||
let length = size.get(side.axis());
|
||||
|
||||
// The arcs for a border of the rectangle along the x-axis, starting at (0,0).
|
||||
let p1 = Point::with_x(start_radius);
|
||||
let mut arc1 = bezier_arc(
|
||||
p1 + Point::new(
|
||||
-angle_left.sin() * start_radius,
|
||||
(1.0 - angle_left.cos()) * start_radius,
|
||||
),
|
||||
Point::new(start_radius, start_radius),
|
||||
p1,
|
||||
);
|
||||
|
||||
let p2 = Point::with_x(length - end_radius);
|
||||
let mut arc2 = bezier_arc(
|
||||
p2,
|
||||
Point::new(length - end_radius, end_radius),
|
||||
p2 + Point::new(
|
||||
angle_right.sin() * end_radius,
|
||||
(1.0 - angle_right.cos()) * end_radius,
|
||||
),
|
||||
);
|
||||
|
||||
let transform = match side {
|
||||
Side::Left => Transform::rotate(Angle::deg(-90.0))
|
||||
.post_concat(Transform::translate(Abs::zero(), size.y)),
|
||||
Side::Bottom => Transform::rotate(Angle::deg(180.0))
|
||||
.post_concat(Transform::translate(size.x, size.y)),
|
||||
Side::Right => Transform::rotate(Angle::deg(90.0))
|
||||
.post_concat(Transform::translate(size.x, Abs::zero())),
|
||||
_ => Transform::identity(),
|
||||
};
|
||||
|
||||
arc1 = arc1.map(|x| x.transform(transform));
|
||||
arc2 = arc2.map(|x| x.transform(transform));
|
||||
|
||||
if !connection.prev {
|
||||
path.move_to(if start_radius.is_zero() { arc1[3] } else { arc1[0] });
|
||||
}
|
||||
|
||||
if !start_radius.is_zero() {
|
||||
path.cubic_to(arc1[1], arc1[2], arc1[3]);
|
||||
}
|
||||
|
||||
path.line_to(arc2[0]);
|
||||
|
||||
if !connection.next && !end_radius.is_zero() {
|
||||
path.cubic_to(arc2[1], arc2[2], arc2[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the control points for a bezier curve that describes a circular arc for
|
||||
/// a start point, an end point and a center of the circle whose arc connects
|
||||
/// the two.
|
||||
fn bezier_arc(start: Point, center: Point, end: Point) -> [Point; 4] {
|
||||
// https://stackoverflow.com/a/44829356/1567835
|
||||
let a = start - center;
|
||||
let b = end - center;
|
||||
|
||||
let q1 = a.x.to_raw() * a.x.to_raw() + a.y.to_raw() * a.y.to_raw();
|
||||
let q2 = q1 + a.x.to_raw() * b.x.to_raw() + a.y.to_raw() * b.y.to_raw();
|
||||
let k2 = (4.0 / 3.0) * ((2.0 * q1 * q2).sqrt() - q2)
|
||||
/ (a.x.to_raw() * b.y.to_raw() - a.y.to_raw() * b.x.to_raw());
|
||||
|
||||
let control_1 = Point::new(center.x + a.x - k2 * a.y, center.y + a.y + k2 * a.x);
|
||||
let control_2 = Point::new(center.x + b.x + k2 * b.y, center.y + b.y - k2 * b.x);
|
||||
|
||||
[start, control_1, control_2, end]
|
||||
}
|
||||
|
||||
/// Indicates which sides of the border strokes in a 2D polygon are connected to
|
||||
/// their neighboring sides.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)]
|
||||
struct Connection {
|
||||
prev: bool,
|
||||
next: bool,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Advance to the next clockwise side of the polygon. The argument
|
||||
/// indicates whether the border is connected on the right side of the next
|
||||
/// edge.
|
||||
pub fn advance(self, next: bool) -> Self {
|
||||
Self { prev: self.next, next }
|
||||
}
|
||||
}
|
@ -74,14 +74,14 @@ impl<T> Sides<T> {
|
||||
|
||||
impl<T: Add> Sides<T> {
|
||||
/// Sums up `left` and `right` into `x`, and `top` and `bottom` into `y`.
|
||||
pub fn sum_by_axis(self) -> Spec<T::Output> {
|
||||
Spec::new(self.left + self.right, self.top + self.bottom)
|
||||
pub fn sum_by_axis(self) -> Axes<T::Output> {
|
||||
Axes::new(self.left + self.right, self.top + self.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sides<Relative<Length>> {
|
||||
impl Sides<Rel<Abs>> {
|
||||
/// Evaluate the sides relative to the given `size`.
|
||||
pub fn relative_to(self, size: Size) -> Sides<Length> {
|
||||
pub fn relative_to(self, size: Size) -> Sides<Abs> {
|
||||
Sides {
|
||||
left: self.left.relative_to(size.x),
|
||||
top: self.top.relative_to(size.y),
|
||||
@ -173,10 +173,10 @@ impl Side {
|
||||
}
|
||||
|
||||
/// Return the corresponding axis.
|
||||
pub fn axis(self) -> SpecAxis {
|
||||
pub fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::Left | Self::Right => SpecAxis::Vertical,
|
||||
Self::Top | Self::Bottom => SpecAxis::Horizontal,
|
||||
Self::Left | Self::Right => Axis::Y,
|
||||
Self::Top | Self::Bottom => Axis::X,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
78
src/geom/size.rs
Normal file
78
src/geom/size.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use super::*;
|
||||
|
||||
/// A size in 2D.
|
||||
pub type Size = Axes<Abs>;
|
||||
|
||||
impl Size {
|
||||
/// The zero value.
|
||||
pub const fn zero() -> Self {
|
||||
Self { x: Abs::zero(), y: Abs::zero() }
|
||||
}
|
||||
|
||||
/// Whether the other size fits into this one (smaller width and height).
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.x.fits(other.x) && self.y.fits(other.y)
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self) -> Point {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Size {
|
||||
fn zero() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
|
||||
fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { x: -self.x, y: -self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { x: self.x + other.x, y: self.y + other.y }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Size - Size -> Size);
|
||||
|
||||
impl Mul<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { x: self.x * other, y: self.y * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Size> for f64 {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: Size) -> Size {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { x: self.x / other, y: self.y / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Size -= Size);
|
||||
assign_impl!(Size += Size);
|
||||
assign_impl!(Size *= f64);
|
||||
assign_impl!(Size /= f64);
|
358
src/geom/spec.rs
358
src/geom/spec.rs
@ -1,358 +0,0 @@
|
||||
use std::any::Any;
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Not};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A container with a horizontal and vertical component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Spec<T> {
|
||||
/// The horizontal component.
|
||||
pub x: T,
|
||||
/// The vertical component.
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T> Spec<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub const fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Create a new instance with two equal components.
|
||||
pub fn splat(v: T) -> Self
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Self { x: v.clone(), y: v }
|
||||
}
|
||||
|
||||
/// Map the individual fields with `f`.
|
||||
pub fn map<F, U>(self, mut f: F) -> Spec<U>
|
||||
where
|
||||
F: FnMut(T) -> U,
|
||||
{
|
||||
Spec { x: f(self.x), y: f(self.y) }
|
||||
}
|
||||
|
||||
/// Convert from `&Spec<T>` to `Spec<&T>`.
|
||||
pub fn as_ref(&self) -> Spec<&T> {
|
||||
Spec { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&Spec<T>` to `Spec<&<T as Deref>::Target>`.
|
||||
pub fn as_deref(&self) -> Spec<&T::Target>
|
||||
where
|
||||
T: Deref,
|
||||
{
|
||||
Spec { x: &self.x, y: &self.y }
|
||||
}
|
||||
|
||||
/// Convert from `&mut Spec<T>` to `Spec<&mut T>`.
|
||||
pub fn as_mut(&mut self) -> Spec<&mut T> {
|
||||
Spec { x: &mut self.x, y: &mut self.y }
|
||||
}
|
||||
|
||||
/// Zip two instances into an instance over a tuple.
|
||||
pub fn zip<U>(self, other: Spec<U>) -> Spec<(T, U)> {
|
||||
Spec {
|
||||
x: (self.x, other.x),
|
||||
y: (self.y, other.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a condition is true for at least one of fields.
|
||||
pub fn any<F>(self, mut f: F) -> bool
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
f(&self.x) || f(&self.y)
|
||||
}
|
||||
|
||||
/// Whether a condition is true for both fields.
|
||||
pub fn all<F>(self, mut f: F) -> bool
|
||||
where
|
||||
F: FnMut(&T) -> bool,
|
||||
{
|
||||
f(&self.x) && f(&self.y)
|
||||
}
|
||||
|
||||
/// Filter the individual fields with a mask.
|
||||
pub fn filter(self, mask: Spec<bool>) -> Spec<Option<T>> {
|
||||
Spec {
|
||||
x: if mask.x { Some(self.x) } else { None },
|
||||
y: if mask.y { Some(self.y) } else { None },
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to the generic representation.
|
||||
pub fn to_gen(self, main: SpecAxis) -> Gen<T> {
|
||||
match main {
|
||||
SpecAxis::Horizontal => Gen::new(self.y, self.x),
|
||||
SpecAxis::Vertical => Gen::new(self.x, self.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Spec<T> {
|
||||
/// Create a new instance with y set to its default value.
|
||||
pub fn with_x(x: T) -> Self {
|
||||
Self { x, y: T::default() }
|
||||
}
|
||||
|
||||
/// Create a new instance with x set to its default value.
|
||||
pub fn with_y(y: T) -> Self {
|
||||
Self { x: T::default(), y }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Ord> Spec<T> {
|
||||
/// The component-wise minimum of this and another instance.
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.min(other.x),
|
||||
y: self.y.min(other.y),
|
||||
}
|
||||
}
|
||||
|
||||
/// The component-wise minimum of this and another instance.
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.max(other.x),
|
||||
y: self.y.max(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Get<SpecAxis> for Spec<T> {
|
||||
type Component = T;
|
||||
|
||||
fn get(self, axis: SpecAxis) -> T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => self.x,
|
||||
SpecAxis::Vertical => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, axis: SpecAxis) -> &mut T {
|
||||
match axis {
|
||||
SpecAxis::Horizontal => &mut self.x,
|
||||
SpecAxis::Vertical => &mut self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Spec<T>
|
||||
where
|
||||
T: Debug + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if let Spec { x: Some(x), y: Some(y) } =
|
||||
self.as_ref().map(|v| (v as &dyn Any).downcast_ref::<Align>())
|
||||
{
|
||||
write!(f, "{:?}-{:?}", x, y)
|
||||
} else if (&self.x as &dyn Any).is::<Length>() {
|
||||
write!(f, "Size({:?}, {:?})", self.x, self.y)
|
||||
} else {
|
||||
write!(f, "Spec({:?}, {:?})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The two specific layouting axes.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SpecAxis {
|
||||
/// The horizontal layouting axis.
|
||||
Horizontal,
|
||||
/// The vertical layouting axis.
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl SpecAxis {
|
||||
/// The direction with the given positivity for this axis.
|
||||
pub fn dir(self, positive: bool) -> Dir {
|
||||
match (self, positive) {
|
||||
(Self::Horizontal, true) => Dir::LTR,
|
||||
(Self::Horizontal, false) => Dir::RTL,
|
||||
(Self::Vertical, true) => Dir::TTB,
|
||||
(Self::Vertical, false) => Dir::BTT,
|
||||
}
|
||||
}
|
||||
|
||||
/// The other axis.
|
||||
pub fn other(self) -> Self {
|
||||
match self {
|
||||
Self::Horizontal => Self::Vertical,
|
||||
Self::Vertical => Self::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SpecAxis {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.pad(match self {
|
||||
Self::Horizontal => "horizontal",
|
||||
Self::Vertical => "vertical",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A size in 2D.
|
||||
pub type Size = Spec<Length>;
|
||||
|
||||
impl Size {
|
||||
/// The zero value.
|
||||
pub const fn zero() -> Self {
|
||||
Self { x: Length::zero(), y: Length::zero() }
|
||||
}
|
||||
|
||||
/// Whether the other size fits into this one (smaller width and height).
|
||||
pub fn fits(self, other: Self) -> bool {
|
||||
self.x.fits(other.x) && self.y.fits(other.y)
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self) -> Point {
|
||||
Point::new(self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for Size {
|
||||
fn zero() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
|
||||
fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self {
|
||||
Self { x: -self.x, y: -self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self { x: self.x + other.x, y: self.y + other.y }
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(Size - Size -> Size);
|
||||
|
||||
impl Mul<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self { x: self.x * other, y: self.y * other }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Size> for f64 {
|
||||
type Output = Size;
|
||||
|
||||
fn mul(self, other: Size) -> Size {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Size {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self { x: self.x / other, y: self.y / other }
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(Size -= Size);
|
||||
assign_impl!(Size += Size);
|
||||
assign_impl!(Size *= f64);
|
||||
assign_impl!(Size /= f64);
|
||||
|
||||
impl<T> Spec<Option<T>> {
|
||||
/// Whether the individual fields are some.
|
||||
pub fn map_is_some(&self) -> Spec<bool> {
|
||||
self.as_ref().map(Option::is_some)
|
||||
}
|
||||
|
||||
/// Whether the individual fields are none.
|
||||
pub fn map_is_none(&self) -> Spec<bool> {
|
||||
self.as_ref().map(Option::is_none)
|
||||
}
|
||||
|
||||
/// Unwrap the individual fields.
|
||||
pub fn unwrap_or(self, other: Spec<T>) -> Spec<T> {
|
||||
Spec {
|
||||
x: self.x.unwrap_or(other.x),
|
||||
y: self.y.unwrap_or(other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Spec<bool> {
|
||||
/// Select `t.x` if `self.x` is true and `f.x` otherwise and same for `y`.
|
||||
pub fn select<T>(self, t: Spec<T>, f: Spec<T>) -> Spec<T> {
|
||||
Spec {
|
||||
x: if self.x { t.x } else { f.x },
|
||||
y: if self.y { t.y } else { f.y },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self { x: !self.x, y: !self.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x | rhs.x, y: self.y | rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<bool> for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x | rhs, y: self.y | rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Self { x: self.x & rhs.x, y: self.y & rhs.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd<bool> for Spec<bool> {
|
||||
type Output = Self;
|
||||
|
||||
fn bitand(self, rhs: bool) -> Self::Output {
|
||||
Self { x: self.x & rhs, y: self.y & rhs }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOrAssign for Spec<bool> {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
self.x |= rhs.x;
|
||||
self.y |= rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAndAssign for Spec<bool> {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
self.x &= rhs.x;
|
||||
self.y &= rhs.y;
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ pub struct Transform {
|
||||
pub ky: Ratio,
|
||||
pub kx: Ratio,
|
||||
pub sy: Ratio,
|
||||
pub tx: Length,
|
||||
pub ty: Length,
|
||||
pub tx: Abs,
|
||||
pub ty: Abs,
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
@ -19,13 +19,13 @@ impl Transform {
|
||||
ky: Ratio::zero(),
|
||||
kx: Ratio::zero(),
|
||||
sy: Ratio::one(),
|
||||
tx: Length::zero(),
|
||||
ty: Length::zero(),
|
||||
tx: Abs::zero(),
|
||||
ty: Abs::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// A translate transform.
|
||||
pub const fn translate(tx: Length, ty: Length) -> Self {
|
||||
pub const fn translate(tx: Abs, ty: Abs) -> Self {
|
||||
Self { tx, ty, ..Self::identity() }
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ impl ImageNode {
|
||||
let height = args.named("height")?;
|
||||
|
||||
Ok(Content::inline(
|
||||
ImageNode(image).pack().sized(Spec::new(width, height)),
|
||||
ImageNode(image).pack().sized(Axes::new(width, height)),
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,7 @@ impl Layout for ImageNode {
|
||||
} else if first.y.is_finite() {
|
||||
Size::new(first.x.min(first.y * px_ratio), first.y)
|
||||
} else {
|
||||
Size::new(Length::pt(pxw), Length::pt(pxh))
|
||||
Size::new(Abs::pt(pxw), Abs::pt(pxh))
|
||||
};
|
||||
|
||||
// Compute the actual size of the fitted image.
|
||||
|
@ -4,9 +4,9 @@ use crate::library::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct LineNode {
|
||||
/// Where the line starts.
|
||||
origin: Spec<Relative<RawLength>>,
|
||||
origin: Axes<Rel<Length>>,
|
||||
/// The offset from the `origin` where the line ends.
|
||||
delta: Spec<Relative<RawLength>>,
|
||||
delta: Axes<Rel<Length>>,
|
||||
}
|
||||
|
||||
#[node]
|
||||
@ -18,18 +18,17 @@ impl LineNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let origin = args.named("origin")?.unwrap_or_default();
|
||||
|
||||
let delta = match args.named::<Spec<Relative<RawLength>>>("to")? {
|
||||
let delta = match args.named::<Axes<Rel<Length>>>("to")? {
|
||||
Some(to) => to.zip(origin).map(|(to, from)| to - from),
|
||||
None => {
|
||||
let length = args
|
||||
.named::<Relative<RawLength>>("length")?
|
||||
.unwrap_or(Length::cm(1.0).into());
|
||||
let length =
|
||||
args.named::<Rel<Length>>("length")?.unwrap_or(Abs::cm(1.0).into());
|
||||
|
||||
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
|
||||
let x = angle.cos() * length;
|
||||
let y = angle.sin() * length;
|
||||
|
||||
Spec::new(x, y)
|
||||
Axes::new(x, y)
|
||||
}
|
||||
};
|
||||
|
||||
@ -69,12 +68,12 @@ impl Layout for LineNode {
|
||||
}
|
||||
|
||||
castable! {
|
||||
Spec<Relative<RawLength>>,
|
||||
Axes<Rel<Length>>,
|
||||
Expected: "array of two relative lengths",
|
||||
Value::Array(array) => {
|
||||
let mut iter = array.into_iter();
|
||||
match (iter.next(), iter.next(), iter.next()) {
|
||||
(Some(a), Some(b), None) => Spec::new(a.cast()?, b.cast()?),
|
||||
(Some(a), Some(b), None) => Axes::new(a.cast()?, b.cast()?),
|
||||
_ => Err("point array must contain exactly two entries")?,
|
||||
}
|
||||
},
|
||||
|
@ -29,20 +29,19 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
||||
|
||||
/// How much to pad the shape's content.
|
||||
#[property(resolve, fold)]
|
||||
pub const INSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||
pub const INSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
||||
/// How much to extend the shape's dimensions beyond the allocated space.
|
||||
#[property(resolve, fold)]
|
||||
pub const OUTSET: Sides<Option<Relative<RawLength>>> = Sides::splat(Relative::zero());
|
||||
pub const OUTSET: Sides<Option<Rel<Length>>> = Sides::splat(Rel::zero());
|
||||
|
||||
/// How much to round the shape's corners.
|
||||
#[property(skip, resolve, fold)]
|
||||
pub const RADIUS: Corners<Option<Relative<RawLength>>> =
|
||||
Corners::splat(Relative::zero());
|
||||
pub const RADIUS: Corners<Option<Rel<Length>>> = Corners::splat(Rel::zero());
|
||||
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let size = match S {
|
||||
SQUARE => args.named::<RawLength>("size")?.map(Relative::from),
|
||||
CIRCLE => args.named::<RawLength>("radius")?.map(|r| 2.0 * Relative::from(r)),
|
||||
SQUARE => args.named::<Length>("size")?.map(Rel::from),
|
||||
CIRCLE => args.named::<Length>("radius")?.map(|r| 2.0 * Rel::from(r)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@ -57,7 +56,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
||||
};
|
||||
|
||||
Ok(Content::inline(
|
||||
Self(args.eat()?).pack().sized(Spec::new(width, height)),
|
||||
Self(args.eat()?).pack().sized(Axes::new(width, height)),
|
||||
))
|
||||
}
|
||||
|
||||
@ -90,7 +89,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
}
|
||||
|
||||
// Pad the child.
|
||||
let child = child.clone().padded(inset.map(|side| side.map(RawLength::from)));
|
||||
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||
|
||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||
frames = child.layout(world, &pod, styles)?;
|
||||
@ -112,14 +111,13 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
};
|
||||
|
||||
pod.first = Size::splat(length);
|
||||
pod.expand = Spec::splat(true);
|
||||
pod.expand = Axes::splat(true);
|
||||
frames = child.layout(world, &pod, styles)?;
|
||||
}
|
||||
} else {
|
||||
// The default size that a shape takes on if it has no child and
|
||||
// enough space.
|
||||
let mut size =
|
||||
Size::new(Length::pt(45.0), Length::pt(30.0)).min(regions.first);
|
||||
let mut size = Size::new(Abs::pt(45.0), Abs::pt(30.0)).min(regions.first);
|
||||
|
||||
if is_quadratic(S) {
|
||||
let length = if regions.expand.x || regions.expand.y {
|
||||
@ -159,16 +157,11 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||
|
||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||
if is_round(S) {
|
||||
let shape = Shape {
|
||||
geometry: Geometry::Ellipse(size),
|
||||
fill,
|
||||
stroke: stroke.left,
|
||||
};
|
||||
let shape = ellipse(size, fill, stroke.left);
|
||||
frame.prepend(pos, Element::Shape(shape));
|
||||
} else {
|
||||
frame.prepend_multiple(
|
||||
RoundedRect::new(size, radius)
|
||||
.shapes(fill, stroke)
|
||||
rounded_rect(size, radius, fill, stroke)
|
||||
.into_iter()
|
||||
.map(|x| (pos, Element::Shape(x))),
|
||||
)
|
||||
|
@ -5,7 +5,7 @@ use crate::library::text::{HorizontalAlign, ParNode};
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct AlignNode {
|
||||
/// How to align the node horizontally and vertically.
|
||||
pub aligns: Spec<Option<RawAlign>>,
|
||||
pub aligns: Axes<Option<RawAlign>>,
|
||||
/// The node to be aligned.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
@ -13,11 +13,11 @@ pub struct AlignNode {
|
||||
#[node]
|
||||
impl AlignNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let aligns: Spec<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
||||
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
||||
let body: Content = args.expect("body")?;
|
||||
Ok(match (body, aligns) {
|
||||
(Content::Block(node), _) => Content::Block(node.aligned(aligns)),
|
||||
(other, Spec { x: Some(x), y: None }) => {
|
||||
(other, Axes { x: Some(x), y: None }) => {
|
||||
other.styled(ParNode::ALIGN, HorizontalAlign(x))
|
||||
}
|
||||
(other, _) => Content::Block(other.pack().aligned(aligns)),
|
||||
@ -34,7 +34,7 @@ impl Layout for AlignNode {
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// The child only needs to expand along an axis if there's no alignment.
|
||||
let mut pod = regions.clone();
|
||||
pod.expand &= self.aligns.map_is_none();
|
||||
pod.expand &= self.aligns.as_ref().map(Option::is_none);
|
||||
|
||||
// Align paragraphs inside the child.
|
||||
let mut passed = StyleMap::new();
|
||||
@ -51,7 +51,7 @@ impl Layout for AlignNode {
|
||||
let aligns = self
|
||||
.aligns
|
||||
.map(|align| align.resolve(styles))
|
||||
.unwrap_or(Spec::new(Align::Left, Align::Top));
|
||||
.unwrap_or(Axes::new(Align::Left, Align::Top));
|
||||
|
||||
frame.resize(target, aligns);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ pub struct ColumnsNode {
|
||||
impl ColumnsNode {
|
||||
/// The size of the gutter space between each column.
|
||||
#[property(resolve)]
|
||||
pub const GUTTER: Relative<RawLength> = Ratio::new(0.04).into();
|
||||
pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
|
||||
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
Ok(Content::block(Self {
|
||||
@ -53,7 +53,7 @@ impl Layout for ColumnsNode {
|
||||
.skip(1)
|
||||
.collect(),
|
||||
last: regions.last,
|
||||
expand: Spec::new(true, regions.expand.y),
|
||||
expand: Axes::new(true, regions.expand.y),
|
||||
};
|
||||
|
||||
// Layout the children.
|
||||
@ -69,9 +69,9 @@ impl Layout for ColumnsNode {
|
||||
// Otherwise its the maximum column height for the frame. In that
|
||||
// case, the frame is first created with zero height and then
|
||||
// resized.
|
||||
let height = if regions.expand.y { region.y } else { Length::zero() };
|
||||
let height = if regions.expand.y { region.y } else { Abs::zero() };
|
||||
let mut output = Frame::new(Size::new(regions.first.x, height));
|
||||
let mut cursor = Length::zero();
|
||||
let mut cursor = Abs::zero();
|
||||
|
||||
for _ in 0 .. columns {
|
||||
let frame = match frames.next() {
|
||||
|
@ -9,7 +9,7 @@ impl BoxNode {
|
||||
let width = args.named("width")?;
|
||||
let height = args.named("height")?;
|
||||
let body: LayoutNode = args.eat()?.unwrap_or_default();
|
||||
Ok(Content::inline(body.sized(Spec::new(width, height))))
|
||||
Ok(Content::inline(body.sized(Axes::new(width, height))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,14 +81,14 @@ pub struct FlowLayouter {
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// Whether the flow should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
expand: Axes<bool>,
|
||||
/// The full size of `regions.size` that was available before we started
|
||||
/// subtracting.
|
||||
full: Size,
|
||||
/// The size used by the frames for the current region.
|
||||
used: Size,
|
||||
/// The sum of fractions in the current region.
|
||||
fr: Fraction,
|
||||
fr: Fr,
|
||||
/// Spacing and layouted nodes.
|
||||
items: Vec<FlowItem>,
|
||||
/// Finished frames for previous regions.
|
||||
@ -98,11 +98,11 @@ pub struct FlowLayouter {
|
||||
/// A prepared item in a flow layout.
|
||||
enum FlowItem {
|
||||
/// Absolute spacing between other items.
|
||||
Absolute(Length),
|
||||
Absolute(Abs),
|
||||
/// Fractional spacing between other items.
|
||||
Fractional(Fraction),
|
||||
Fractional(Fr),
|
||||
/// A frame for a layouted child node and how to align it.
|
||||
Frame(Frame, Spec<Align>),
|
||||
Frame(Frame, Axes<Align>),
|
||||
/// An absolutely placed frame.
|
||||
Placed(Frame),
|
||||
}
|
||||
@ -122,7 +122,7 @@ impl FlowLayouter {
|
||||
expand,
|
||||
full,
|
||||
used: Size::zero(),
|
||||
fr: Fraction::zero(),
|
||||
fr: Fr::zero(),
|
||||
items: vec![],
|
||||
finished: vec![],
|
||||
}
|
||||
@ -169,7 +169,7 @@ impl FlowLayouter {
|
||||
}
|
||||
|
||||
// How to align the node.
|
||||
let aligns = Spec::new(
|
||||
let aligns = Axes::new(
|
||||
// For non-expanding paragraphs it is crucial that we align the
|
||||
// whole paragraph as it is itself aligned.
|
||||
styles.get(ParNode::ALIGN),
|
||||
@ -215,7 +215,7 @@ impl FlowLayouter {
|
||||
}
|
||||
|
||||
let mut output = Frame::new(size);
|
||||
let mut offset = Length::zero();
|
||||
let mut offset = Abs::zero();
|
||||
let mut ruler = Align::Top;
|
||||
|
||||
// Place all frames.
|
||||
@ -245,7 +245,7 @@ impl FlowLayouter {
|
||||
self.regions.next();
|
||||
self.full = self.regions.first;
|
||||
self.used = Size::zero();
|
||||
self.fr = Fraction::zero();
|
||||
self.fr = Fr::zero();
|
||||
self.finished.push(output);
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,9 @@ use crate::library::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct GridNode {
|
||||
/// Defines sizing for content rows and columns.
|
||||
pub tracks: Spec<Vec<TrackSizing>>,
|
||||
pub tracks: Axes<Vec<TrackSizing>>,
|
||||
/// Defines sizing of gutter rows and columns between content.
|
||||
pub gutter: Spec<Vec<TrackSizing>>,
|
||||
pub gutter: Axes<Vec<TrackSizing>>,
|
||||
/// The nodes to be arranged in a grid.
|
||||
pub cells: Vec<LayoutNode>,
|
||||
}
|
||||
@ -20,8 +20,8 @@ impl GridNode {
|
||||
let column_gutter = args.named("column-gutter")?;
|
||||
let row_gutter = args.named("row-gutter")?;
|
||||
Ok(Content::block(Self {
|
||||
tracks: Spec::new(columns, rows),
|
||||
gutter: Spec::new(
|
||||
tracks: Axes::new(columns, rows),
|
||||
gutter: Axes::new(
|
||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||
row_gutter.unwrap_or(base_gutter),
|
||||
),
|
||||
@ -59,10 +59,10 @@ pub enum TrackSizing {
|
||||
Auto,
|
||||
/// A track size specified in absolute terms and relative to the parent's
|
||||
/// size.
|
||||
Relative(Relative<RawLength>),
|
||||
Relative(Rel<Length>),
|
||||
/// A track size specified as a fraction of the remaining free space in the
|
||||
/// parent.
|
||||
Fractional(Fraction),
|
||||
Fractional(Fr),
|
||||
}
|
||||
|
||||
castable! {
|
||||
@ -105,16 +105,16 @@ pub struct GridLayouter<'a> {
|
||||
/// The inherited styles.
|
||||
styles: StyleChain<'a>,
|
||||
/// Resolved column sizes.
|
||||
rcols: Vec<Length>,
|
||||
rcols: Vec<Abs>,
|
||||
/// Rows in the current region.
|
||||
lrows: Vec<Row>,
|
||||
/// The full height of the current region.
|
||||
full: Length,
|
||||
full: Abs,
|
||||
/// The used-up size of the current region. The horizontal size is
|
||||
/// determined once after columns are resolved and not touched again.
|
||||
used: Size,
|
||||
/// The sum of fractions in the current region.
|
||||
fr: Fraction,
|
||||
fr: Fr,
|
||||
/// Frames for finished regions.
|
||||
finished: Vec<Frame>,
|
||||
}
|
||||
@ -125,7 +125,7 @@ enum Row {
|
||||
/// Finished row frame of auto or relative row.
|
||||
Frame(Frame),
|
||||
/// Fractional row with y index.
|
||||
Fr(Fraction, usize),
|
||||
Fr(Fr, usize),
|
||||
}
|
||||
|
||||
impl<'a> GridLayouter<'a> {
|
||||
@ -134,8 +134,8 @@ impl<'a> GridLayouter<'a> {
|
||||
/// This prepares grid layout by unifying content and gutter tracks.
|
||||
pub fn new(
|
||||
world: Tracked<'a, dyn World>,
|
||||
tracks: Spec<&[TrackSizing]>,
|
||||
gutter: Spec<&[TrackSizing]>,
|
||||
tracks: Axes<&[TrackSizing]>,
|
||||
gutter: Axes<&[TrackSizing]>,
|
||||
cells: &'a [LayoutNode],
|
||||
regions: &Regions,
|
||||
styles: StyleChain<'a>,
|
||||
@ -156,7 +156,7 @@ impl<'a> GridLayouter<'a> {
|
||||
};
|
||||
|
||||
let auto = TrackSizing::Auto;
|
||||
let zero = TrackSizing::Relative(Relative::zero());
|
||||
let zero = TrackSizing::Relative(Rel::zero());
|
||||
let get_or = |tracks: &[_], idx, default| {
|
||||
tracks.get(idx).or(tracks.last()).copied().unwrap_or(default)
|
||||
};
|
||||
@ -178,13 +178,13 @@ impl<'a> GridLayouter<'a> {
|
||||
rows.pop();
|
||||
|
||||
let full = regions.first.y;
|
||||
let rcols = vec![Length::zero(); cols.len()];
|
||||
let rcols = vec![Abs::zero(); cols.len()];
|
||||
let lrows = vec![];
|
||||
|
||||
// We use the regions for auto row measurement. Since at that moment,
|
||||
// columns are already sized, we can enable horizontal expansion.
|
||||
let mut regions = regions.clone();
|
||||
regions.expand = Spec::new(true, false);
|
||||
regions.expand = Axes::new(true, false);
|
||||
|
||||
Self {
|
||||
world,
|
||||
@ -197,7 +197,7 @@ impl<'a> GridLayouter<'a> {
|
||||
lrows,
|
||||
full,
|
||||
used: Size::zero(),
|
||||
fr: Fraction::zero(),
|
||||
fr: Fr::zero(),
|
||||
finished: vec![],
|
||||
}
|
||||
}
|
||||
@ -230,10 +230,10 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Determine all column sizes.
|
||||
fn measure_columns(&mut self) -> SourceResult<()> {
|
||||
// Sum of sizes of resolved relative tracks.
|
||||
let mut rel = Length::zero();
|
||||
let mut rel = Abs::zero();
|
||||
|
||||
// Sum of fractions of all fractional tracks.
|
||||
let mut fr = Fraction::zero();
|
||||
let mut fr = Fr::zero();
|
||||
|
||||
// Resolve the size of all relative columns and compute the sum of all
|
||||
// fractional tracks.
|
||||
@ -252,14 +252,14 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Size that is not used by fixed-size columns.
|
||||
let available = self.regions.first.x - rel;
|
||||
if available >= Length::zero() {
|
||||
if available >= Abs::zero() {
|
||||
// Determine size of auto columns.
|
||||
let (auto, count) = self.measure_auto_columns(available)?;
|
||||
|
||||
// If there is remaining space, distribute it to fractional columns,
|
||||
// otherwise shrink auto columns.
|
||||
let remaining = available - auto;
|
||||
if remaining >= Length::zero() {
|
||||
if remaining >= Abs::zero() {
|
||||
if !fr.is_zero() {
|
||||
self.grow_fractional_columns(remaining, fr);
|
||||
}
|
||||
@ -275,11 +275,8 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Measure the size that is available to auto columns.
|
||||
fn measure_auto_columns(
|
||||
&mut self,
|
||||
available: Length,
|
||||
) -> SourceResult<(Length, usize)> {
|
||||
let mut auto = Length::zero();
|
||||
fn measure_auto_columns(&mut self, available: Abs) -> SourceResult<(Abs, usize)> {
|
||||
let mut auto = Abs::zero();
|
||||
let mut count = 0;
|
||||
|
||||
// Determine size of auto columns by laying out all cells in those
|
||||
@ -289,12 +286,12 @@ impl<'a> GridLayouter<'a> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut resolved = Length::zero();
|
||||
let mut resolved = Abs::zero();
|
||||
for y in 0 .. self.rows.len() {
|
||||
if let Some(node) = self.cell(x, y) {
|
||||
let size = Size::new(available, self.regions.base.y);
|
||||
let mut pod =
|
||||
Regions::one(size, self.regions.base, Spec::splat(false));
|
||||
Regions::one(size, self.regions.base, Axes::splat(false));
|
||||
|
||||
// For relative rows, we can already resolve the correct
|
||||
// base, for auto it's already correct and for fr we could
|
||||
@ -318,7 +315,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Distribute remaining space to fractional columns.
|
||||
fn grow_fractional_columns(&mut self, remaining: Length, fr: Fraction) {
|
||||
fn grow_fractional_columns(&mut self, remaining: Abs, fr: Fr) {
|
||||
for (&col, rcol) in self.cols.iter().zip(&mut self.rcols) {
|
||||
if let TrackSizing::Fractional(v) = col {
|
||||
*rcol = v.share(fr, remaining);
|
||||
@ -327,7 +324,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Redistribute space to auto columns so that each gets a fair share.
|
||||
fn shrink_auto_columns(&mut self, available: Length, count: usize) {
|
||||
fn shrink_auto_columns(&mut self, available: Abs, count: usize) {
|
||||
// The fair share each auto column may have.
|
||||
let fair = available / count as f64;
|
||||
|
||||
@ -359,7 +356,7 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Layout a row with automatic height. Such a row may break across multiple
|
||||
/// regions.
|
||||
fn layout_auto_row(&mut self, y: usize) -> SourceResult<()> {
|
||||
let mut resolved: Vec<Length> = vec![];
|
||||
let mut resolved: Vec<Abs> = vec![];
|
||||
|
||||
// Determine the size for each region of the row.
|
||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||
@ -426,11 +423,7 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
/// Layout a row with relative height. Such a row cannot break across
|
||||
/// multiple regions, but it may force a region break.
|
||||
fn layout_relative_row(
|
||||
&mut self,
|
||||
v: Relative<RawLength>,
|
||||
y: usize,
|
||||
) -> SourceResult<()> {
|
||||
fn layout_relative_row(&mut self, v: Rel<Length>, y: usize) -> SourceResult<()> {
|
||||
let resolved = v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||
let frame = self.layout_single_row(resolved, y)?;
|
||||
|
||||
@ -451,7 +444,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
/// Layout a row with fixed height and return its frame.
|
||||
fn layout_single_row(&mut self, height: Length, y: usize) -> SourceResult<Frame> {
|
||||
fn layout_single_row(&mut self, height: Abs, y: usize) -> SourceResult<Frame> {
|
||||
let mut output = Frame::new(Size::new(self.used.x, height));
|
||||
|
||||
let mut pos = Point::zero();
|
||||
@ -462,11 +455,11 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Set the base to the region's base for auto rows and to the
|
||||
// size for relative and fractional rows.
|
||||
let base = Spec::new(self.cols[x], self.rows[y])
|
||||
let base = Axes::new(self.cols[x], self.rows[y])
|
||||
.map(|s| s == TrackSizing::Auto)
|
||||
.select(self.regions.base, size);
|
||||
|
||||
let pod = Regions::one(size, base, Spec::splat(true));
|
||||
let pod = Regions::one(size, base, Axes::splat(true));
|
||||
let frame = node.layout(self.world, &pod, self.styles)?.remove(0);
|
||||
match frame.role() {
|
||||
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||
@ -488,7 +481,7 @@ impl<'a> GridLayouter<'a> {
|
||||
/// Layout a row spanning multiple regions.
|
||||
fn layout_multi_row(
|
||||
&mut self,
|
||||
heights: &[Length],
|
||||
heights: &[Abs],
|
||||
y: usize,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// Prepare frames.
|
||||
@ -499,7 +492,7 @@ impl<'a> GridLayouter<'a> {
|
||||
|
||||
// Prepare regions.
|
||||
let size = Size::new(self.used.x, heights[0]);
|
||||
let mut pod = Regions::one(size, self.regions.base, Spec::splat(true));
|
||||
let mut pod = Regions::one(size, self.regions.base, Axes::splat(true));
|
||||
pod.backlog = heights[1 ..].to_vec();
|
||||
|
||||
// Layout the row.
|
||||
@ -573,8 +566,8 @@ impl<'a> GridLayouter<'a> {
|
||||
self.finished.push(output);
|
||||
self.regions.next();
|
||||
self.full = self.regions.first.y;
|
||||
self.used.y = Length::zero();
|
||||
self.fr = Fraction::zero();
|
||||
self.used.y = Abs::zero();
|
||||
self.fr = Fr::zero();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::library::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct PadNode {
|
||||
/// The amount of padding.
|
||||
pub padding: Sides<Relative<RawLength>>,
|
||||
pub padding: Sides<Rel<Length>>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
@ -54,7 +54,7 @@ impl Layout for PadNode {
|
||||
}
|
||||
|
||||
/// Shrink a size by padding relative to the size itself.
|
||||
fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
|
||||
fn shrink(size: Size, padding: Sides<Rel<Abs>>) -> Size {
|
||||
size - padding.relative_to(size).sum_by_axis()
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ fn shrink(size: Size, padding: Sides<Relative<Length>>) -> Size {
|
||||
/// <=> w - p.rel * w - p.abs = s
|
||||
/// <=> (1 - p.rel) * w = s + p.abs
|
||||
/// <=> w = (s + p.abs) / (1 - p.rel)
|
||||
fn grow(size: Size, padding: Sides<Relative<Length>>) -> Size {
|
||||
fn grow(size: Size, padding: Sides<Rel<Abs>>) -> Size {
|
||||
size.zip(padding.sum_by_axis())
|
||||
.map(|(s, p)| (s + p.abs).safe_div(1.0 - p.rel.get()))
|
||||
}
|
||||
|
@ -11,17 +11,16 @@ pub struct PageNode(pub LayoutNode);
|
||||
impl PageNode {
|
||||
/// The unflipped width of the page.
|
||||
#[property(resolve)]
|
||||
pub const WIDTH: Smart<RawLength> = Smart::Custom(Paper::A4.width().into());
|
||||
pub const WIDTH: Smart<Length> = Smart::Custom(Paper::A4.width().into());
|
||||
/// The unflipped height of the page.
|
||||
#[property(resolve)]
|
||||
pub const HEIGHT: Smart<RawLength> = Smart::Custom(Paper::A4.height().into());
|
||||
pub const HEIGHT: Smart<Length> = Smart::Custom(Paper::A4.height().into());
|
||||
/// Whether the page is flipped into landscape orientation.
|
||||
pub const FLIPPED: bool = false;
|
||||
|
||||
/// The page's margins.
|
||||
#[property(fold)]
|
||||
pub const MARGINS: Sides<Option<Smart<Relative<RawLength>>>> =
|
||||
Sides::splat(Smart::Auto);
|
||||
pub const MARGINS: Sides<Option<Smart<Rel<Length>>>> = Sides::splat(Smart::Auto);
|
||||
|
||||
/// How many columns the page has.
|
||||
pub const COLUMNS: NonZeroUsize = NonZeroUsize::new(1).unwrap();
|
||||
@ -63,8 +62,8 @@ impl PageNode {
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// When one of the lengths is infinite the page fits its content along
|
||||
// that axis.
|
||||
let width = styles.get(Self::WIDTH).unwrap_or(Length::inf());
|
||||
let height = styles.get(Self::HEIGHT).unwrap_or(Length::inf());
|
||||
let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
|
||||
let height = styles.get(Self::HEIGHT).unwrap_or(Abs::inf());
|
||||
let mut size = Size::new(width, height);
|
||||
if styles.get(Self::FLIPPED) {
|
||||
std::mem::swap(&mut size.x, &mut size.y);
|
||||
@ -76,7 +75,7 @@ impl PageNode {
|
||||
}
|
||||
|
||||
// Determine the margins.
|
||||
let default = Relative::from(0.1190 * min);
|
||||
let default = Rel::from(0.1190 * min);
|
||||
let padding = styles.get(Self::MARGINS).map(|side| side.unwrap_or(default));
|
||||
|
||||
let mut child = self.0.clone();
|
||||
@ -96,7 +95,7 @@ impl PageNode {
|
||||
}
|
||||
|
||||
// Layout the child.
|
||||
let regions = Regions::repeat(size, size, size.map(Length::is_finite));
|
||||
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
||||
let mut frames = child.layout(world, ®ions, styles)?;
|
||||
|
||||
let header = styles.get(Self::HEADER);
|
||||
@ -127,7 +126,7 @@ impl PageNode {
|
||||
(Role::Background, background, Point::zero(), size),
|
||||
] {
|
||||
if let Some(content) = marginal.resolve(world, page)? {
|
||||
let pod = Regions::one(area, area, Spec::splat(true));
|
||||
let pod = Regions::one(area, area, Axes::splat(true));
|
||||
let mut sub = content.layout(world, &pod, styles)?.remove(0);
|
||||
sub.apply_role(role);
|
||||
|
||||
@ -224,13 +223,13 @@ pub struct Paper {
|
||||
|
||||
impl Paper {
|
||||
/// The width of the paper.
|
||||
pub fn width(self) -> Length {
|
||||
Length::mm(self.width)
|
||||
pub fn width(self) -> Abs {
|
||||
Abs::mm(self.width)
|
||||
}
|
||||
|
||||
/// The height of the paper.
|
||||
pub fn height(self) -> Length {
|
||||
Length::mm(self.height)
|
||||
pub fn height(self) -> Abs {
|
||||
Abs::mm(self.height)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,12 @@ pub struct PlaceNode(pub LayoutNode);
|
||||
#[node]
|
||||
impl PlaceNode {
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let aligns = args.find()?.unwrap_or(Spec::with_x(Some(RawAlign::Start)));
|
||||
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
|
||||
let dx = args.named("dx")?.unwrap_or_default();
|
||||
let dy = args.named("dy")?.unwrap_or_default();
|
||||
let body: LayoutNode = args.expect("body")?;
|
||||
Ok(Content::block(Self(
|
||||
body.moved(Spec::new(dx, dy)).aligned(aligns),
|
||||
body.moved(Axes::new(dx, dy)).aligned(aligns),
|
||||
)))
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,7 @@ impl Layout for PlaceNode {
|
||||
// The pod is the base area of the region because for absolute
|
||||
// placement we don't really care about the already used area.
|
||||
let pod = {
|
||||
let finite = regions.base.map(Length::is_finite);
|
||||
let finite = regions.base.map(Abs::is_finite);
|
||||
let expand = finite & (regions.expand | out_of_flow);
|
||||
Regions::one(regions.base, regions.base, expand)
|
||||
};
|
||||
|
@ -31,10 +31,10 @@ impl VNode {
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Spacing {
|
||||
/// Spacing specified in absolute terms and relative to the parent's size.
|
||||
Relative(Relative<RawLength>),
|
||||
Relative(Rel<Length>),
|
||||
/// Spacing specified as a fraction of the remaining free space in the
|
||||
/// parent.
|
||||
Fractional(Fraction),
|
||||
Fractional(Fr),
|
||||
}
|
||||
|
||||
impl Spacing {
|
||||
@ -44,9 +44,9 @@ impl Spacing {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for Spacing {
|
||||
fn from(length: Length) -> Self {
|
||||
Self::Relative(length.into())
|
||||
impl From<Abs> for Spacing {
|
||||
fn from(abs: Abs) -> Self {
|
||||
Self::Relative(abs.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,12 +71,12 @@ castable! {
|
||||
|
||||
/// Spacing around and between block-level nodes, relative to paragraph spacing.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct BlockSpacing(Relative<RawLength>);
|
||||
pub struct BlockSpacing(Rel<Length>);
|
||||
|
||||
castable!(BlockSpacing: Relative<RawLength>);
|
||||
castable!(BlockSpacing: Rel<Length>);
|
||||
|
||||
impl Resolve for BlockSpacing {
|
||||
type Output = Length;
|
||||
type Output = Abs;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
let whole = styles.get(ParNode::SPACING);
|
||||
|
@ -90,19 +90,19 @@ pub struct StackLayouter<'a> {
|
||||
/// The stacking direction.
|
||||
dir: Dir,
|
||||
/// The axis of the stacking direction.
|
||||
axis: SpecAxis,
|
||||
axis: Axis,
|
||||
/// The regions to layout children into.
|
||||
regions: Regions,
|
||||
/// The inherited styles.
|
||||
styles: StyleChain<'a>,
|
||||
/// Whether the stack itself should expand to fill the region.
|
||||
expand: Spec<bool>,
|
||||
expand: Axes<bool>,
|
||||
/// The full size of the current region that was available at the start.
|
||||
full: Size,
|
||||
/// The generic size used by the frames for the current region.
|
||||
used: Gen<Length>,
|
||||
used: Gen<Abs>,
|
||||
/// The sum of fractions in the current region.
|
||||
fr: Fraction,
|
||||
fr: Fr,
|
||||
/// Already layouted items whose exact positions are not yet known due to
|
||||
/// fractional spacing.
|
||||
items: Vec<StackItem>,
|
||||
@ -113,9 +113,9 @@ pub struct StackLayouter<'a> {
|
||||
/// A prepared item in a stack layout.
|
||||
enum StackItem {
|
||||
/// Absolute spacing between other items.
|
||||
Absolute(Length),
|
||||
Absolute(Abs),
|
||||
/// Fractional spacing between other items.
|
||||
Fractional(Fraction),
|
||||
Fractional(Fr),
|
||||
/// A frame for a layouted child node.
|
||||
Frame(Frame, Align),
|
||||
}
|
||||
@ -139,7 +139,7 @@ impl<'a> StackLayouter<'a> {
|
||||
expand,
|
||||
full,
|
||||
used: Gen::zero(),
|
||||
fr: Fraction::zero(),
|
||||
fr: Fr::zero(),
|
||||
items: vec![],
|
||||
finished: vec![],
|
||||
}
|
||||
@ -200,7 +200,12 @@ impl<'a> StackLayouter<'a> {
|
||||
frame.apply_role(Role::GenericBlock);
|
||||
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
let size = frame.size().to_gen(self.axis);
|
||||
let size = frame.size();
|
||||
let size = match self.axis {
|
||||
Axis::X => Gen::new(size.y, size.x),
|
||||
Axis::Y => Gen::new(size.x, size.y),
|
||||
};
|
||||
|
||||
self.used.main += size.main;
|
||||
self.used.cross.set_max(size.cross);
|
||||
*self.regions.first.get_mut(self.axis) -= size.main;
|
||||
@ -218,7 +223,7 @@ impl<'a> StackLayouter<'a> {
|
||||
pub fn finish_region(&mut self) {
|
||||
// Determine the size of the stack in this region dependening on whether
|
||||
// the region expands.
|
||||
let used = self.used.to_spec(self.axis);
|
||||
let used = self.used.to_axes(self.axis);
|
||||
let mut size = self.expand.select(self.full, used);
|
||||
|
||||
// Expand fully if there are fr spacings.
|
||||
@ -230,7 +235,7 @@ impl<'a> StackLayouter<'a> {
|
||||
}
|
||||
|
||||
let mut output = Frame::new(size);
|
||||
let mut cursor = Length::zero();
|
||||
let mut cursor = Abs::zero();
|
||||
let mut ruler: Align = self.dir.start().into();
|
||||
|
||||
// Place all frames.
|
||||
@ -255,7 +260,7 @@ impl<'a> StackLayouter<'a> {
|
||||
self.used.main - child - cursor
|
||||
};
|
||||
|
||||
let pos = Gen::new(Length::zero(), block).to_point(self.axis);
|
||||
let pos = Gen::new(Abs::zero(), block).to_point(self.axis);
|
||||
cursor += child;
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
@ -266,7 +271,7 @@ impl<'a> StackLayouter<'a> {
|
||||
self.regions.next();
|
||||
self.full = self.regions.first;
|
||||
self.used = Gen::zero();
|
||||
self.fr = Fraction::zero();
|
||||
self.fr = Fr::zero();
|
||||
self.finished.push(output);
|
||||
}
|
||||
|
||||
@ -276,3 +281,39 @@ impl<'a> StackLayouter<'a> {
|
||||
self.finished
|
||||
}
|
||||
}
|
||||
|
||||
/// A container with a main and cross component.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Gen<T> {
|
||||
/// The main component.
|
||||
pub cross: T,
|
||||
/// The cross component.
|
||||
pub main: T,
|
||||
}
|
||||
|
||||
impl<T> Gen<T> {
|
||||
/// Create a new instance from the two components.
|
||||
pub const fn new(cross: T, main: T) -> Self {
|
||||
Self { cross, main }
|
||||
}
|
||||
|
||||
/// Convert to the specific representation, given the current main axis.
|
||||
pub fn to_axes(self, main: Axis) -> Axes<T> {
|
||||
match main {
|
||||
Axis::X => Axes::new(self.main, self.cross),
|
||||
Axis::Y => Axes::new(self.cross, self.main),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Gen<Abs> {
|
||||
/// The zero value.
|
||||
pub fn zero() -> Self {
|
||||
Self { cross: Abs::zero(), main: Abs::zero() }
|
||||
}
|
||||
|
||||
/// Convert to a point.
|
||||
pub fn to_point(self, main: Axis) -> Point {
|
||||
self.to_axes(main).to_point()
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::library::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct MoveNode {
|
||||
/// The offset by which to move the node.
|
||||
pub delta: Spec<Relative<RawLength>>,
|
||||
pub delta: Axes<Rel<Length>>,
|
||||
/// The node whose contents should be moved.
|
||||
pub child: LayoutNode,
|
||||
}
|
||||
@ -16,7 +16,7 @@ impl MoveNode {
|
||||
let dx = args.named("dx")?.unwrap_or_default();
|
||||
let dy = args.named("dy")?.unwrap_or_default();
|
||||
Ok(Content::inline(Self {
|
||||
delta: Spec::new(dx, dy),
|
||||
delta: Axes::new(dx, dy),
|
||||
child: args.expect("body")?,
|
||||
}))
|
||||
}
|
||||
@ -60,7 +60,7 @@ pub type ScaleNode = TransformNode<SCALE>;
|
||||
impl<const T: TransformKind> TransformNode<T> {
|
||||
/// The origin of the transformation.
|
||||
#[property(resolve)]
|
||||
pub const ORIGIN: Spec<Option<RawAlign>> = Spec::default();
|
||||
pub const ORIGIN: Axes<Option<RawAlign>> = Axes::default();
|
||||
|
||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
let transform = match T {
|
||||
@ -94,7 +94,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||
let mut frames = self.child.layout(world, regions, styles)?;
|
||||
|
||||
for frame in &mut frames {
|
||||
let Spec { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||
let transform = Transform::translate(x, y)
|
||||
.pre_concat(self.transform)
|
||||
.pre_concat(Transform::translate(-x, -y));
|
||||
|
@ -92,7 +92,7 @@ impl Show for MathNode {
|
||||
Ok(if self.display() {
|
||||
Content::block(
|
||||
LayoutNode::new(self.clone())
|
||||
.aligned(Spec::with_x(Some(Align::Center.into()))),
|
||||
.aligned(Axes::with_x(Some(Align::Center.into()))),
|
||||
)
|
||||
} else {
|
||||
Content::inline(self.clone())
|
||||
@ -171,9 +171,9 @@ fn layout_tex(
|
||||
|
||||
// Determine the metrics.
|
||||
let (x0, y0, x1, y1) = renderer.size(&layout);
|
||||
let width = Length::pt(x1 - x0);
|
||||
let mut top = Length::pt(y1);
|
||||
let mut bottom = Length::pt(-y0);
|
||||
let width = Abs::pt(x1 - x0);
|
||||
let mut top = Abs::pt(y1);
|
||||
let mut bottom = Abs::pt(-y0);
|
||||
if style != Style::Display {
|
||||
let metrics = font.metrics();
|
||||
top = styles.get(TextNode::TOP_EDGE).resolve(styles, metrics);
|
||||
@ -204,7 +204,7 @@ fn layout_tex(
|
||||
/// A ReX rendering backend that renders into a frame.
|
||||
struct FrameBackend {
|
||||
frame: Frame,
|
||||
baseline: Length,
|
||||
baseline: Abs,
|
||||
font: Font,
|
||||
fill: Paint,
|
||||
lang: Lang,
|
||||
@ -222,7 +222,7 @@ impl FrameBackend {
|
||||
|
||||
/// Convert a cursor to a point.
|
||||
fn transform(&self, cursor: Cursor) -> Point {
|
||||
Point::new(Length::pt(cursor.x), self.baseline + Length::pt(cursor.y))
|
||||
Point::new(Abs::pt(cursor.x), self.baseline + Abs::pt(cursor.y))
|
||||
}
|
||||
}
|
||||
|
||||
@ -232,7 +232,7 @@ impl Backend for FrameBackend {
|
||||
self.transform(pos),
|
||||
Element::Text(Text {
|
||||
font: self.font.clone(),
|
||||
size: Length::pt(scale),
|
||||
size: Abs::pt(scale),
|
||||
fill: self.fill(),
|
||||
lang: self.lang,
|
||||
glyphs: vec![Glyph {
|
||||
@ -249,10 +249,7 @@ impl Backend for FrameBackend {
|
||||
self.frame.push(
|
||||
self.transform(pos),
|
||||
Element::Shape(Shape {
|
||||
geometry: Geometry::Rect(Size::new(
|
||||
Length::pt(width),
|
||||
Length::pt(height),
|
||||
)),
|
||||
geometry: Geometry::Rect(Size::new(Abs::pt(width), Abs::pt(height))),
|
||||
fill: Some(self.fill()),
|
||||
stroke: None,
|
||||
}),
|
||||
|
@ -218,17 +218,17 @@ impl StyleMapExt for StyleMap {
|
||||
/// Additional methods for layout nodes.
|
||||
pub trait LayoutNodeExt {
|
||||
/// Set alignments for this node.
|
||||
fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self;
|
||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
|
||||
|
||||
/// Pad this node at the sides.
|
||||
fn padded(self, padding: Sides<Relative<RawLength>>) -> Self;
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
|
||||
|
||||
/// Transform this node's contents without affecting layout.
|
||||
fn moved(self, delta: Spec<Relative<RawLength>>) -> Self;
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
||||
}
|
||||
|
||||
impl LayoutNodeExt for LayoutNode {
|
||||
fn aligned(self, aligns: Spec<Option<RawAlign>>) -> Self {
|
||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
|
||||
if aligns.any(Option::is_some) {
|
||||
layout::AlignNode { aligns, child: self }.pack()
|
||||
} else {
|
||||
@ -236,7 +236,7 @@ impl LayoutNodeExt for LayoutNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn padded(self, padding: Sides<Relative<RawLength>>) -> Self {
|
||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||
if !padding.left.is_zero()
|
||||
|| !padding.top.is_zero()
|
||||
|| !padding.right.is_zero()
|
||||
@ -248,7 +248,7 @@ impl LayoutNodeExt for LayoutNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn moved(self, delta: Spec<Relative<RawLength>>) -> Self {
|
||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||
if delta.any(|r| !r.is_zero()) {
|
||||
layout::MoveNode { delta, child: self }.pack()
|
||||
} else {
|
||||
|
@ -16,12 +16,9 @@ pub use crate::diag::{
|
||||
pub use crate::frame::*;
|
||||
pub use crate::geom::*;
|
||||
pub use crate::model::{
|
||||
Arg, Args, Array, Cast, Dict, Dynamic, Func, Node, RawAlign, RawLength, RawStroke,
|
||||
Scope, Smart, Str, Value, Vm,
|
||||
};
|
||||
pub use crate::model::{
|
||||
Content, Fold, Key, Layout, LayoutNode, Regions, Resolve, Selector, Show, ShowNode,
|
||||
StyleChain, StyleMap, StyleVec,
|
||||
Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, LayoutNode,
|
||||
Node, RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, ShowNode, Smart,
|
||||
Str, StyleChain, StyleMap, StyleVec, Value, Vm,
|
||||
};
|
||||
pub use crate::syntax::{Span, Spanned};
|
||||
pub use crate::util::EcoString;
|
||||
|
@ -29,10 +29,10 @@ impl<const L: ListKind> ListNode<L> {
|
||||
pub const LABEL: Label = Label::Default;
|
||||
/// The indentation of each item's label.
|
||||
#[property(resolve)]
|
||||
pub const INDENT: RawLength = RawLength::zero();
|
||||
pub const INDENT: Length = Length::zero();
|
||||
/// The space between the label and the body of each item.
|
||||
#[property(resolve)]
|
||||
pub const BODY_INDENT: RawLength = Em::new(match L {
|
||||
pub const BODY_INDENT: Length = Em::new(match L {
|
||||
LIST | ENUM => 0.5,
|
||||
DESC | _ => 1.0,
|
||||
})
|
||||
@ -159,13 +159,13 @@ impl<const L: ListKind> Show for ListNode<L> {
|
||||
}
|
||||
|
||||
Ok(Content::block(GridNode {
|
||||
tracks: Spec::with_x(vec![
|
||||
tracks: Axes::with_x(vec![
|
||||
TrackSizing::Relative(indent.into()),
|
||||
TrackSizing::Auto,
|
||||
TrackSizing::Relative(body_indent.into()),
|
||||
TrackSizing::Auto,
|
||||
]),
|
||||
gutter: Spec::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
||||
gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
||||
cells,
|
||||
}))
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ use crate::library::prelude::*;
|
||||
#[derive(Debug, Hash)]
|
||||
pub struct TableNode {
|
||||
/// Defines sizing for content rows and columns.
|
||||
pub tracks: Spec<Vec<TrackSizing>>,
|
||||
pub tracks: Axes<Vec<TrackSizing>>,
|
||||
/// Defines sizing of gutter rows and columns between content.
|
||||
pub gutter: Spec<Vec<TrackSizing>>,
|
||||
pub gutter: Axes<Vec<TrackSizing>>,
|
||||
/// The nodes to be arranged in the table.
|
||||
pub cells: Vec<Content>,
|
||||
}
|
||||
@ -21,7 +21,7 @@ impl TableNode {
|
||||
#[property(resolve, fold)]
|
||||
pub const STROKE: Option<RawStroke> = Some(RawStroke::default());
|
||||
/// How much to pad the cells's content.
|
||||
pub const PADDING: Relative<RawLength> = Length::pt(5.0).into();
|
||||
pub const PADDING: Rel<Length> = Abs::pt(5.0).into();
|
||||
|
||||
/// The spacing above the table.
|
||||
#[property(resolve, shorthand(around))]
|
||||
@ -37,8 +37,8 @@ impl TableNode {
|
||||
let column_gutter = args.named("column-gutter")?;
|
||||
let row_gutter = args.named("row-gutter")?;
|
||||
Ok(Content::show(Self {
|
||||
tracks: Spec::new(columns, rows),
|
||||
gutter: Spec::new(
|
||||
tracks: Axes::new(columns, rows),
|
||||
gutter: Axes::new(
|
||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||
row_gutter.unwrap_or(base_gutter),
|
||||
),
|
||||
|
@ -26,10 +26,10 @@ impl<const L: DecoLine> DecoNode<L> {
|
||||
/// Position of the line relative to the baseline, read from the font tables
|
||||
/// if `auto`.
|
||||
#[property(resolve)]
|
||||
pub const OFFSET: Smart<RawLength> = Smart::Auto;
|
||||
pub const OFFSET: Smart<Length> = Smart::Auto;
|
||||
/// Amount that the line will be longer or shorter than its associated text.
|
||||
#[property(resolve)]
|
||||
pub const EXTENT: RawLength = RawLength::zero();
|
||||
pub const EXTENT: Length = Length::zero();
|
||||
/// Whether the line skips sections in which it would collide
|
||||
/// with the glyphs. Does not apply to strikethrough.
|
||||
pub const EVADE: bool = true;
|
||||
@ -69,9 +69,9 @@ impl<const L: DecoLine> Show for DecoNode<L> {
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Decoration {
|
||||
pub line: DecoLine,
|
||||
pub stroke: RawStroke<Length>,
|
||||
pub offset: Smart<Length>,
|
||||
pub extent: Length,
|
||||
pub stroke: RawStroke<Abs>,
|
||||
pub offset: Smart<Abs>,
|
||||
pub extent: Abs,
|
||||
pub evade: bool,
|
||||
}
|
||||
|
||||
@ -92,9 +92,9 @@ pub fn decorate(
|
||||
frame: &mut Frame,
|
||||
deco: &Decoration,
|
||||
text: &Text,
|
||||
shift: Length,
|
||||
shift: Abs,
|
||||
pos: Point,
|
||||
width: Length,
|
||||
width: Abs,
|
||||
) {
|
||||
let font_metrics = text.font.metrics();
|
||||
let metrics = match deco.line {
|
||||
@ -116,9 +116,9 @@ pub fn decorate(
|
||||
let mut start = pos.x - deco.extent;
|
||||
let end = pos.x + (width + 2.0 * deco.extent);
|
||||
|
||||
let mut push_segment = |from: Length, to: Length| {
|
||||
let mut push_segment = |from: Abs, to: Abs| {
|
||||
let origin = Point::new(from, pos.y + offset);
|
||||
let target = Point::new(to - from, Length::zero());
|
||||
let target = Point::new(to - from, Abs::zero());
|
||||
|
||||
if target.x >= min_width || !evade {
|
||||
let shape = Geometry::Line(target).stroked(stroke);
|
||||
@ -161,7 +161,7 @@ pub fn decorate(
|
||||
intersections.extend(
|
||||
path.segments()
|
||||
.flat_map(|seg| seg.intersect_line(line))
|
||||
.map(|is| Length::raw(line.eval(is.line_t).x)),
|
||||
.map(|is| Abs::raw(line.eval(is.line_t).x)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -196,12 +196,12 @@ pub fn decorate(
|
||||
struct BezPathBuilder {
|
||||
path: BezPath,
|
||||
units_per_em: f64,
|
||||
font_size: Length,
|
||||
font_size: Abs,
|
||||
x_offset: f64,
|
||||
}
|
||||
|
||||
impl BezPathBuilder {
|
||||
fn new(units_per_em: f64, font_size: Length, x_offset: f64) -> Self {
|
||||
fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self {
|
||||
Self {
|
||||
path: BezPath::new(),
|
||||
units_per_em,
|
||||
|
@ -43,9 +43,9 @@ castable! {
|
||||
Value::Str(string) => Self::Url(string.into()),
|
||||
Value::Dict(dict) => {
|
||||
let page = dict.get("page")?.clone().cast()?;
|
||||
let x: RawLength = dict.get("x")?.clone().cast()?;
|
||||
let y: RawLength = dict.get("y")?.clone().cast()?;
|
||||
Self::Internal(Location { page, pos: Point::new(x.length, y.length) })
|
||||
let x: Length = dict.get("x")?.clone().cast()?;
|
||||
let y: Length = dict.get("y")?.clone().cast()?;
|
||||
Self::Internal(Location { page, pos: Point::new(x.abs, y.abs) })
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -48,19 +48,19 @@ impl TextNode {
|
||||
|
||||
/// The size of the glyphs.
|
||||
#[property(shorthand, fold)]
|
||||
pub const SIZE: TextSize = Length::pt(11.0);
|
||||
pub const SIZE: TextSize = Abs::pt(11.0);
|
||||
/// The glyph fill color.
|
||||
#[property(shorthand)]
|
||||
pub const FILL: Paint = Color::BLACK.into();
|
||||
/// The amount of space that should be added between characters.
|
||||
#[property(resolve)]
|
||||
pub const TRACKING: RawLength = RawLength::zero();
|
||||
pub const TRACKING: Length = Length::zero();
|
||||
/// The width of spaces relative to the font's space width.
|
||||
#[property(resolve)]
|
||||
pub const SPACING: Relative<RawLength> = Relative::one();
|
||||
pub const SPACING: Rel<Length> = Rel::one();
|
||||
/// The offset of the baseline.
|
||||
#[property(resolve)]
|
||||
pub const BASELINE: RawLength = RawLength::zero();
|
||||
pub const BASELINE: Length = Length::zero();
|
||||
/// Whether certain glyphs can hang over into the margin.
|
||||
pub const OVERHANG: bool = true;
|
||||
/// The top end of the text bounding box.
|
||||
@ -243,17 +243,17 @@ castable! {
|
||||
|
||||
/// The size of text.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct TextSize(pub RawLength);
|
||||
pub struct TextSize(pub Length);
|
||||
|
||||
impl Fold for TextSize {
|
||||
type Output = Length;
|
||||
type Output = Abs;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.0.em.at(outer) + self.0.length
|
||||
self.0.em.at(outer) + self.0.abs
|
||||
}
|
||||
}
|
||||
|
||||
castable!(TextSize: RawLength);
|
||||
castable!(TextSize: Length);
|
||||
|
||||
/// Specifies the bottom or top edge of text.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
@ -261,12 +261,12 @@ pub enum TextEdge {
|
||||
/// An edge specified using one of the well-known font metrics.
|
||||
Metric(VerticalFontMetric),
|
||||
/// An edge specified as a length.
|
||||
Length(RawLength),
|
||||
Length(Length),
|
||||
}
|
||||
|
||||
impl TextEdge {
|
||||
/// Resolve the value of the text edge given a font's metrics.
|
||||
pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Length {
|
||||
pub fn resolve(self, styles: StyleChain, metrics: &FontMetrics) -> Abs {
|
||||
match self {
|
||||
Self::Metric(metric) => metrics.vertical(metric).resolve(styles),
|
||||
Self::Length(length) => length.resolve(styles),
|
||||
@ -310,8 +310,8 @@ castable! {
|
||||
HorizontalDir,
|
||||
Expected: "direction",
|
||||
@dir: Dir => match dir.axis() {
|
||||
SpecAxis::Horizontal => Self(*dir),
|
||||
SpecAxis::Vertical => Err("must be horizontal")?,
|
||||
Axis::X => Self(*dir),
|
||||
Axis::Y => Err("must be horizontal")?,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -30,13 +30,13 @@ pub enum ParChild {
|
||||
impl ParNode {
|
||||
/// The spacing between lines.
|
||||
#[property(resolve)]
|
||||
pub const LEADING: RawLength = Em::new(0.65).into();
|
||||
pub const LEADING: Length = Em::new(0.65).into();
|
||||
/// The extra spacing between paragraphs.
|
||||
#[property(resolve)]
|
||||
pub const SPACING: RawLength = Em::new(1.2).into();
|
||||
pub const SPACING: Length = Em::new(1.2).into();
|
||||
/// The indent the first line of a consecutive paragraph should have.
|
||||
#[property(resolve)]
|
||||
pub const INDENT: RawLength = RawLength::zero();
|
||||
pub const INDENT: Length = Length::zero();
|
||||
/// Whether to allow paragraph spacing when there is paragraph indent.
|
||||
pub const SPACING_AND_INDENT: bool = false;
|
||||
|
||||
@ -119,8 +119,8 @@ castable! {
|
||||
HorizontalAlign,
|
||||
Expected: "alignment",
|
||||
@align: RawAlign => match align.axis() {
|
||||
SpecAxis::Horizontal => Self(*align),
|
||||
SpecAxis::Vertical => Err("must be horizontal")?,
|
||||
Axis::X => Self(*align),
|
||||
Axis::Y => Err("must be horizontal")?,
|
||||
},
|
||||
}
|
||||
|
||||
@ -212,7 +212,7 @@ struct Preparation<'a> {
|
||||
/// The text language if it's the same for all children.
|
||||
lang: Option<Lang>,
|
||||
/// The resolved leading between lines.
|
||||
leading: Length,
|
||||
leading: Abs,
|
||||
/// The paragraph's resolved alignment.
|
||||
align: Align,
|
||||
/// Whether to justify the paragraph.
|
||||
@ -292,9 +292,9 @@ enum Item<'a> {
|
||||
/// A shaped text run with consistent style and direction.
|
||||
Text(ShapedText<'a>),
|
||||
/// Absolute spacing between other items.
|
||||
Absolute(Length),
|
||||
Absolute(Abs),
|
||||
/// Fractional spacing between other items.
|
||||
Fractional(Fraction),
|
||||
Fractional(Fr),
|
||||
/// A layouted child node.
|
||||
Frame(Frame),
|
||||
/// A repeating node that fills the remaining space.
|
||||
@ -320,12 +320,12 @@ impl<'a> Item<'a> {
|
||||
}
|
||||
|
||||
/// The natural layouted width of the item.
|
||||
fn width(&self) -> Length {
|
||||
fn width(&self) -> Abs {
|
||||
match self {
|
||||
Self::Text(shaped) => shaped.width,
|
||||
Self::Absolute(v) => *v,
|
||||
Self::Frame(frame) => frame.width(),
|
||||
Self::Fractional(_) | Self::Repeat(_, _) => Length::zero(),
|
||||
Self::Fractional(_) | Self::Repeat(_, _) => Abs::zero(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,7 +354,7 @@ struct Line<'a> {
|
||||
/// there is only one text item, this takes precedence over `first`.
|
||||
last: Option<Item<'a>>,
|
||||
/// The width of the line.
|
||||
width: Length,
|
||||
width: Abs,
|
||||
/// Whether the line should be justified.
|
||||
justify: bool,
|
||||
/// Whether the line ends with a hyphen or dash, either naturally or through
|
||||
@ -402,8 +402,8 @@ impl<'a> Line<'a> {
|
||||
}
|
||||
|
||||
/// How much of the line is stretchable spaces.
|
||||
fn stretch(&self) -> Length {
|
||||
let mut stretch = Length::zero();
|
||||
fn stretch(&self) -> Abs {
|
||||
let mut stretch = Abs::zero();
|
||||
for shaped in self.items().filter_map(Item::text) {
|
||||
stretch += shaped.stretch();
|
||||
}
|
||||
@ -411,11 +411,11 @@ impl<'a> Line<'a> {
|
||||
}
|
||||
|
||||
/// The sum of fractions in the line.
|
||||
fn fr(&self) -> Fraction {
|
||||
fn fr(&self) -> Fr {
|
||||
self.items()
|
||||
.filter_map(|item| match item {
|
||||
Item::Fractional(fr) => Some(*fr),
|
||||
Item::Repeat(_, _) => Some(Fraction::one()),
|
||||
Item::Repeat(_, _) => Some(Fr::one()),
|
||||
_ => None,
|
||||
})
|
||||
.sum()
|
||||
@ -533,7 +533,7 @@ fn prepare<'a>(
|
||||
items.push(Item::Repeat(repeat, styles));
|
||||
} else {
|
||||
let size = Size::new(regions.first.x, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Spec::splat(false));
|
||||
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
||||
let mut frame = node.layout(world, &pod, styles)?.remove(0);
|
||||
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
||||
frame.apply_role(Role::GenericInline);
|
||||
@ -628,7 +628,7 @@ fn shared_get<'a, K: Key<'a>>(
|
||||
fn linebreak<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Length,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
match p.styles.get(ParNode::LINEBREAKS) {
|
||||
Linebreaks::Simple => linebreak_simple(p, world, width),
|
||||
@ -642,7 +642,7 @@ fn linebreak<'a>(
|
||||
fn linebreak_simple<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Length,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
let mut lines = vec![];
|
||||
let mut start = 0;
|
||||
@ -702,7 +702,7 @@ fn linebreak_simple<'a>(
|
||||
fn linebreak_optimized<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Length,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
/// The cost of a line or paragraph layout.
|
||||
type Cost = f64;
|
||||
@ -930,7 +930,7 @@ fn line<'a>(
|
||||
first: None,
|
||||
inner: &[],
|
||||
last: None,
|
||||
width: Length::zero(),
|
||||
width: Abs::zero(),
|
||||
justify,
|
||||
dash: false,
|
||||
};
|
||||
@ -938,7 +938,7 @@ fn line<'a>(
|
||||
|
||||
// Slice out the relevant items.
|
||||
let (expanded, mut inner) = p.slice(range.clone());
|
||||
let mut width = Length::zero();
|
||||
let mut width = Abs::zero();
|
||||
|
||||
// Reshape the last item if it's split in half or hyphenated.
|
||||
let mut last = None;
|
||||
@ -1075,10 +1075,10 @@ fn commit(
|
||||
world: Tracked<dyn World>,
|
||||
line: &Line,
|
||||
regions: &Regions,
|
||||
width: Length,
|
||||
width: Abs,
|
||||
) -> SourceResult<Frame> {
|
||||
let mut remaining = width - line.width;
|
||||
let mut offset = Length::zero();
|
||||
let mut offset = Abs::zero();
|
||||
|
||||
// Reorder the line from logical to visual order.
|
||||
let reordered = reorder(line);
|
||||
@ -1112,22 +1112,22 @@ fn commit(
|
||||
|
||||
// Determine how much to justify each space.
|
||||
let fr = line.fr();
|
||||
let mut justification = Length::zero();
|
||||
if remaining < Length::zero() || (line.justify && fr.is_zero()) {
|
||||
let mut justification = Abs::zero();
|
||||
if remaining < Abs::zero() || (line.justify && fr.is_zero()) {
|
||||
let justifiables = line.justifiables();
|
||||
if justifiables > 0 {
|
||||
justification = remaining / justifiables as f64;
|
||||
remaining = Length::zero();
|
||||
remaining = Abs::zero();
|
||||
}
|
||||
}
|
||||
|
||||
let mut top = Length::zero();
|
||||
let mut bottom = Length::zero();
|
||||
let mut top = Abs::zero();
|
||||
let mut bottom = Abs::zero();
|
||||
|
||||
// Build the frames and determine the height and baseline.
|
||||
let mut frames = vec![];
|
||||
for item in reordered {
|
||||
let mut push = |offset: &mut Length, frame: Frame| {
|
||||
let mut push = |offset: &mut Abs, frame: Frame| {
|
||||
let width = frame.width();
|
||||
top.set_max(frame.baseline());
|
||||
bottom.set_max(frame.size().y - frame.baseline());
|
||||
@ -1151,9 +1151,9 @@ fn commit(
|
||||
}
|
||||
Item::Repeat(node, styles) => {
|
||||
let before = offset;
|
||||
let fill = Fraction::one().share(fr, remaining);
|
||||
let fill = Fr::one().share(fr, remaining);
|
||||
let size = Size::new(fill, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Spec::new(false, false));
|
||||
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
||||
let frame = node.layout(world, &pod, *styles)?.remove(0);
|
||||
let width = frame.width();
|
||||
let count = (fill / width).floor();
|
||||
@ -1162,7 +1162,7 @@ fn commit(
|
||||
if count == 1.0 {
|
||||
offset += p.align.position(remaining);
|
||||
}
|
||||
if width > Length::zero() {
|
||||
if width > Abs::zero() {
|
||||
for _ in 0 .. (count as usize).min(1000) {
|
||||
push(&mut offset, frame.clone());
|
||||
offset += apart;
|
||||
@ -1175,7 +1175,7 @@ fn commit(
|
||||
|
||||
// Remaining space is distributed now.
|
||||
if !fr.is_zero() {
|
||||
remaining = Length::zero();
|
||||
remaining = Abs::zero();
|
||||
}
|
||||
|
||||
let size = Size::new(width, top + bottom);
|
||||
|
@ -23,9 +23,9 @@ pub struct ShapedText<'a> {
|
||||
/// The font variant.
|
||||
pub variant: FontVariant,
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
pub size: Abs,
|
||||
/// The width of the text's bounding box.
|
||||
pub width: Length,
|
||||
pub width: Abs,
|
||||
/// The shaped glyphs.
|
||||
pub glyphs: Cow<'a, [ShapedGlyph]>,
|
||||
}
|
||||
@ -80,11 +80,11 @@ impl<'a> ShapedText<'a> {
|
||||
///
|
||||
/// The `justification` defines how much extra advance width each
|
||||
/// [justifiable glyph](ShapedGlyph::is_justifiable) will get.
|
||||
pub fn build(&self, world: Tracked<dyn World>, justification: Length) -> Frame {
|
||||
pub fn build(&self, world: Tracked<dyn World>, justification: Abs) -> Frame {
|
||||
let (top, bottom) = self.measure(world);
|
||||
let size = Size::new(self.width, top + bottom);
|
||||
|
||||
let mut offset = Length::zero();
|
||||
let mut offset = Abs::zero();
|
||||
let mut frame = Frame::new(size);
|
||||
frame.set_baseline(top);
|
||||
|
||||
@ -144,9 +144,9 @@ impl<'a> ShapedText<'a> {
|
||||
}
|
||||
|
||||
/// Measure the top and bottom extent of this text.
|
||||
fn measure(&self, world: Tracked<dyn World>) -> (Length, Length) {
|
||||
let mut top = Length::zero();
|
||||
let mut bottom = Length::zero();
|
||||
fn measure(&self, world: Tracked<dyn World>) -> (Abs, Abs) {
|
||||
let mut top = Abs::zero();
|
||||
let mut bottom = Abs::zero();
|
||||
|
||||
let top_edge = self.styles.get(TextNode::TOP_EDGE);
|
||||
let bottom_edge = self.styles.get(TextNode::BOTTOM_EDGE);
|
||||
@ -186,7 +186,7 @@ impl<'a> ShapedText<'a> {
|
||||
}
|
||||
|
||||
/// The width of the spaces in the text.
|
||||
pub fn stretch(&self) -> Length {
|
||||
pub fn stretch(&self) -> Abs {
|
||||
self.glyphs
|
||||
.iter()
|
||||
.filter(|g| g.is_justifiable())
|
||||
@ -310,7 +310,7 @@ struct ShapingContext<'a> {
|
||||
glyphs: Vec<ShapedGlyph>,
|
||||
used: Vec<Font>,
|
||||
styles: StyleChain<'a>,
|
||||
size: Length,
|
||||
size: Abs,
|
||||
variant: FontVariant,
|
||||
tags: Vec<rustybuzz::Feature>,
|
||||
fallback: bool,
|
||||
|
@ -23,7 +23,7 @@ impl<const S: ScriptKind> ShiftNode<S> {
|
||||
/// font.
|
||||
pub const TYPOGRAPHIC: bool = true;
|
||||
/// The baseline shift for synthetic sub- and superscripts.
|
||||
pub const BASELINE: RawLength =
|
||||
pub const BASELINE: Length =
|
||||
Em::new(if S == SUPERSCRIPT { -0.5 } else { 0.2 }).into();
|
||||
/// The font size for synthetic sub- and superscripts.
|
||||
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
||||
|
@ -15,7 +15,7 @@ use super::{
|
||||
};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::frame::{Frame, Role};
|
||||
use crate::geom::{Length, Numeric};
|
||||
use crate::geom::{Abs, Numeric};
|
||||
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
||||
use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
|
||||
use crate::library::text::{ParChild, ParNode};
|
||||
@ -175,7 +175,7 @@ impl Content {
|
||||
}
|
||||
|
||||
/// Add weak vertical spacing above and below the node.
|
||||
pub fn spaced(self, above: Option<Length>, below: Option<Length>) -> Self {
|
||||
pub fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
|
||||
if above.is_none() && below.is_none() {
|
||||
return self;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use super::{
|
||||
Pattern, Recipe, Scope, Scopes, StyleEntry, StyleMap, Value, Vm,
|
||||
};
|
||||
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
|
||||
use crate::geom::{Angle, Em, Fraction, Length, Ratio};
|
||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||
use crate::library;
|
||||
use crate::syntax::ast::TypedNode;
|
||||
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
|
||||
@ -465,10 +465,10 @@ impl Eval for ast::Lit {
|
||||
ast::LitKind::Int(v) => Value::Int(v),
|
||||
ast::LitKind::Float(v) => Value::Float(v),
|
||||
ast::LitKind::Numeric(v, unit) => match unit {
|
||||
Unit::Length(unit) => Length::with_unit(v, unit).into(),
|
||||
Unit::Length(unit) => Abs::with_unit(v, unit).into(),
|
||||
Unit::Angle(unit) => Angle::with_unit(v, unit).into(),
|
||||
Unit::Em => Em::new(v).into(),
|
||||
Unit::Fr => Fraction::new(v).into(),
|
||||
Unit::Fr => Fr::new(v).into(),
|
||||
Unit::Percent => Ratio::new(v / 100.0).into(),
|
||||
},
|
||||
ast::LitKind::Str(v) => Value::Str(v.into()),
|
||||
|
@ -8,10 +8,10 @@ use std::sync::Arc;
|
||||
use comemo::{Prehashed, Tracked};
|
||||
|
||||
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
||||
use super::{Builder, Content, RawLength, Scratch};
|
||||
use super::{Builder, Content, Scratch};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::frame::{Element, Frame};
|
||||
use crate::geom::{Align, Geometry, Length, Paint, Point, Relative, Size, Spec, Stroke};
|
||||
use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke};
|
||||
use crate::World;
|
||||
|
||||
/// Layout content into a collection of pages.
|
||||
@ -56,18 +56,18 @@ pub struct Regions {
|
||||
/// The base size for relative sizing.
|
||||
pub base: Size,
|
||||
/// The height of followup regions. The width is the same for all regions.
|
||||
pub backlog: Vec<Length>,
|
||||
pub backlog: Vec<Abs>,
|
||||
/// The height of the final region that is repeated once the backlog is
|
||||
/// drained. The width is the same for all regions.
|
||||
pub last: Option<Length>,
|
||||
pub last: Option<Abs>,
|
||||
/// Whether nodes should expand to fill the regions instead of shrinking to
|
||||
/// fit the content.
|
||||
pub expand: Spec<bool>,
|
||||
pub expand: Axes<bool>,
|
||||
}
|
||||
|
||||
impl Regions {
|
||||
/// Create a new region sequence with exactly one region.
|
||||
pub fn one(size: Size, base: Size, expand: Spec<bool>) -> Self {
|
||||
pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
||||
Self {
|
||||
first: size,
|
||||
base,
|
||||
@ -78,7 +78,7 @@ impl Regions {
|
||||
}
|
||||
|
||||
/// Create a new sequence of same-size regions that repeats indefinitely.
|
||||
pub fn repeat(size: Size, base: Size, expand: Spec<bool>) -> Self {
|
||||
pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
||||
Self {
|
||||
first: size,
|
||||
base,
|
||||
@ -108,7 +108,7 @@ impl Regions {
|
||||
|
||||
/// Whether the first region is full and a region break is called for.
|
||||
pub fn is_full(&self) -> bool {
|
||||
Length::zero().fits(self.first.y) && !self.in_last()
|
||||
Abs::zero().fits(self.first.y) && !self.in_last()
|
||||
}
|
||||
|
||||
/// Whether the first region is the last usable region.
|
||||
@ -173,7 +173,7 @@ impl LayoutNode {
|
||||
}
|
||||
|
||||
/// Force a size for this node.
|
||||
pub fn sized(self, sizing: Spec<Option<Relative<RawLength>>>) -> Self {
|
||||
pub fn sized(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
||||
if sizing.any(Option::is_some) {
|
||||
SizedNode { sizing, child: self }.pack()
|
||||
} else {
|
||||
@ -279,7 +279,7 @@ impl Layout for EmptyNode {
|
||||
#[derive(Debug, Hash)]
|
||||
struct SizedNode {
|
||||
/// How to size the node horizontally and vertically.
|
||||
sizing: Spec<Option<Relative<RawLength>>>,
|
||||
sizing: Axes<Option<Rel<Length>>>,
|
||||
/// The node to be sized.
|
||||
child: LayoutNode,
|
||||
}
|
||||
@ -303,7 +303,7 @@ impl Layout for SizedNode {
|
||||
|
||||
// Select the appropriate base and expansion for the child depending
|
||||
// on whether it is automatically or relatively sized.
|
||||
let is_auto = self.sizing.map_is_none();
|
||||
let is_auto = self.sizing.as_ref().map(Option::is_none);
|
||||
let base = is_auto.select(regions.base, size);
|
||||
let expand = regions.expand | !is_auto;
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::{RawAlign, RawLength, RawStroke, Regex, Smart, Value};
|
||||
use super::{RawAlign, RawStroke, Regex, Smart, Value};
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Numeric, Relative, Spec, SpecAxis};
|
||||
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
|
||||
use Value::*;
|
||||
|
||||
/// Bail with a type mismatch error.
|
||||
@ -106,8 +106,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
{
|
||||
if a.axis() != b.axis() {
|
||||
Value::dynamic(match a.axis() {
|
||||
SpecAxis::Horizontal => Spec { x: a, y: b },
|
||||
SpecAxis::Vertical => Spec { x: b, y: a },
|
||||
Axis::X => Axes { x: a, y: b },
|
||||
Axis::Y => Axes { x: b, y: a },
|
||||
})
|
||||
} else {
|
||||
return Err(format!("cannot add two {:?} alignments", a.axis()));
|
||||
@ -203,8 +203,8 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
(Length(a), Int(b)) => Length(a / b as f64),
|
||||
(Length(a), Float(b)) => Length(a / b),
|
||||
(Length(a), Length(b)) => Float(div_length(a, b)?),
|
||||
(Length(a), Relative(b)) if b.rel.is_zero() => Float(div_length(a, b.abs)?),
|
||||
(Length(a), Length(b)) => Float(try_div_length(a, b)?),
|
||||
(Length(a), Relative(b)) if b.rel.is_zero() => Float(try_div_length(a, b.abs)?),
|
||||
|
||||
(Angle(a), Int(b)) => Angle(a / b as f64),
|
||||
(Angle(a), Float(b)) => Angle(a / b),
|
||||
@ -217,9 +217,9 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
|
||||
(Relative(a), Int(b)) => Relative(a / b as f64),
|
||||
(Relative(a), Float(b)) => Relative(a / b),
|
||||
(Relative(a), Length(b)) if a.rel.is_zero() => Float(div_length(a.abs, b)?),
|
||||
(Relative(a), Length(b)) if a.rel.is_zero() => Float(try_div_length(a.abs, b)?),
|
||||
(Relative(a), Ratio(b)) if a.abs.is_zero() => Float(a.rel / b),
|
||||
(Relative(a), Relative(b)) => Float(div_relative(a, b)?),
|
||||
(Relative(a), Relative(b)) => Float(try_div_relative(a, b)?),
|
||||
|
||||
(Fraction(a), Int(b)) => Fraction(a / b as f64),
|
||||
(Fraction(a), Float(b)) => Fraction(a / b),
|
||||
@ -230,25 +230,14 @@ pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> {
|
||||
}
|
||||
|
||||
/// Try to divide two lengths.
|
||||
fn div_length(a: RawLength, b: RawLength) -> StrResult<f64> {
|
||||
if a.length.is_zero() && b.length.is_zero() {
|
||||
Ok(a.em / b.em)
|
||||
} else if a.em.is_zero() && b.em.is_zero() {
|
||||
Ok(a.length / b.length)
|
||||
} else {
|
||||
return Err("cannot divide these two lengths".into());
|
||||
}
|
||||
fn try_div_length(a: Length, b: Length) -> StrResult<f64> {
|
||||
a.try_div(b).ok_or_else(|| "cannot divide these two lengths".into())
|
||||
}
|
||||
|
||||
/// Try to divide two relative lengths.
|
||||
fn div_relative(a: Relative<RawLength>, b: Relative<RawLength>) -> StrResult<f64> {
|
||||
if a.rel.is_zero() && b.rel.is_zero() {
|
||||
div_length(a.abs, b.abs)
|
||||
} else if a.abs.is_zero() && b.abs.is_zero() {
|
||||
Ok(a.rel / b.rel)
|
||||
} else {
|
||||
return Err("cannot divide these two relative lengths".into());
|
||||
}
|
||||
fn try_div_relative(a: Rel<Length>, b: Rel<Length>) -> StrResult<f64> {
|
||||
a.try_div(b)
|
||||
.ok_or_else(|| "cannot divide these two relative lengths".into())
|
||||
}
|
||||
|
||||
/// Compute the logical "not" of a value.
|
||||
|
@ -5,11 +5,11 @@ use std::sync::Arc;
|
||||
|
||||
use comemo::Prehashed;
|
||||
|
||||
use super::{Interruption, NodeId, RawLength, Smart, StyleChain};
|
||||
use crate::geom::{Corners, Length, Numeric, Relative, Sides, Spec};
|
||||
use super::{Interruption, NodeId, Smart, StyleChain};
|
||||
use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
|
||||
use crate::library::layout::PageNode;
|
||||
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
||||
use crate::library::text::ParNode;
|
||||
use crate::library::text::{ParNode, TextNode};
|
||||
use crate::util::ReadableTypeId;
|
||||
|
||||
/// A style property originating from a set rule or constructor.
|
||||
@ -163,6 +163,26 @@ pub trait Resolve {
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output;
|
||||
}
|
||||
|
||||
impl Resolve for Em {
|
||||
type Output = Abs;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
if self.is_zero() {
|
||||
Abs::zero()
|
||||
} else {
|
||||
self.at(styles.get(TextNode::SIZE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for Length {
|
||||
type Output = Abs;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.abs + self.em.resolve(styles)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resolve> Resolve for Option<T> {
|
||||
type Output = Option<T::Output>;
|
||||
|
||||
@ -179,8 +199,8 @@ impl<T: Resolve> Resolve for Smart<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resolve> Resolve for Spec<T> {
|
||||
type Output = Spec<T::Output>;
|
||||
impl<T: Resolve> Resolve for Axes<T> {
|
||||
type Output = Axes<T::Output>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.map(|v| v.resolve(styles))
|
||||
@ -203,12 +223,12 @@ impl<T: Resolve> Resolve for Corners<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Resolve for Relative<T>
|
||||
impl<T> Resolve for Rel<T>
|
||||
where
|
||||
T: Resolve + Numeric,
|
||||
<T as Resolve>::Output: Numeric,
|
||||
{
|
||||
type Output = Relative<<T as Resolve>::Output>;
|
||||
type Output = Rel<<T as Resolve>::Output>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.map(|abs| abs.resolve(styles))
|
||||
@ -259,16 +279,16 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for Sides<Option<Relative<Length>>> {
|
||||
type Output = Sides<Relative<Length>>;
|
||||
impl Fold for Sides<Option<Rel<Abs>>> {
|
||||
type Output = Sides<Rel<Abs>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for Sides<Option<Smart<Relative<RawLength>>>> {
|
||||
type Output = Sides<Smart<Relative<RawLength>>>;
|
||||
impl Fold for Sides<Option<Smart<Rel<Length>>>> {
|
||||
type Output = Sides<Smart<Rel<Length>>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
@ -286,8 +306,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for Corners<Option<Relative<Length>>> {
|
||||
type Output = Corners<Relative<Length>>;
|
||||
impl Fold for Corners<Option<Rel<Abs>>> {
|
||||
type Output = Corners<Rel<Abs>>;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||
|
170
src/model/raw.rs
170
src/model/raw.rs
@ -1,11 +1,7 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::{Add, Div, Mul, Neg};
|
||||
|
||||
use super::{Fold, Resolve, Smart, StyleChain, Value};
|
||||
use crate::geom::{
|
||||
Align, Em, Get, Length, Numeric, Paint, Relative, Spec, SpecAxis, Stroke,
|
||||
};
|
||||
use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
|
||||
use crate::library::text::TextNode;
|
||||
|
||||
/// The unresolved alignment representation.
|
||||
@ -34,9 +30,9 @@ impl Resolve for RawAlign {
|
||||
|
||||
impl RawAlign {
|
||||
/// The axis this alignment belongs to.
|
||||
pub const fn axis(self) -> SpecAxis {
|
||||
pub const fn axis(self) -> Axis {
|
||||
match self {
|
||||
Self::Start | Self::End => SpecAxis::Horizontal,
|
||||
Self::Start | Self::End => Axis::X,
|
||||
Self::Specific(align) => align.axis(),
|
||||
}
|
||||
}
|
||||
@ -51,8 +47,8 @@ impl From<Align> for RawAlign {
|
||||
impl Debug for RawAlign {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Start => f.pad("left"),
|
||||
Self::End => f.pad("center"),
|
||||
Self::Start => f.pad("start"),
|
||||
Self::End => f.pad("end"),
|
||||
Self::Specific(align) => align.fmt(f),
|
||||
}
|
||||
}
|
||||
@ -63,18 +59,18 @@ dynamic! {
|
||||
}
|
||||
|
||||
dynamic! {
|
||||
Spec<RawAlign>: "2d alignment",
|
||||
Axes<RawAlign>: "2d alignment",
|
||||
}
|
||||
|
||||
castable! {
|
||||
Spec<Option<RawAlign>>,
|
||||
Axes<Option<RawAlign>>,
|
||||
Expected: "1d or 2d alignment",
|
||||
@align: RawAlign => {
|
||||
let mut aligns = Spec::default();
|
||||
let mut aligns = Axes::default();
|
||||
aligns.set(align.axis(), Some(*align));
|
||||
aligns
|
||||
},
|
||||
@aligns: Spec<RawAlign> => aligns.map(Some),
|
||||
@aligns: Axes<RawAlign> => aligns.map(Some),
|
||||
}
|
||||
|
||||
/// The unresolved stroke representation.
|
||||
@ -83,14 +79,14 @@ castable! {
|
||||
/// just a paint (`red`), just a thickness (`0.1em`) or both (`2pt + red`) where
|
||||
/// this is expected.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct RawStroke<T = RawLength> {
|
||||
pub struct RawStroke<T = Length> {
|
||||
/// The stroke's paint.
|
||||
pub paint: Smart<Paint>,
|
||||
/// The stroke's thickness.
|
||||
pub thickness: Smart<T>,
|
||||
}
|
||||
|
||||
impl RawStroke<Length> {
|
||||
impl RawStroke<Abs> {
|
||||
/// Unpack the stroke, filling missing fields from the `default`.
|
||||
pub fn unwrap_or(self, default: Stroke) -> Stroke {
|
||||
Stroke {
|
||||
@ -106,7 +102,7 @@ impl RawStroke<Length> {
|
||||
}
|
||||
|
||||
impl Resolve for RawStroke {
|
||||
type Output = RawStroke<Length>;
|
||||
type Output = RawStroke<Abs>;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
RawStroke {
|
||||
@ -116,7 +112,7 @@ impl Resolve for RawStroke {
|
||||
}
|
||||
}
|
||||
|
||||
impl Fold for RawStroke<Length> {
|
||||
impl Fold for RawStroke<Abs> {
|
||||
type Output = Self;
|
||||
|
||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||
@ -151,143 +147,3 @@ dynamic! {
|
||||
thickness: Smart::Auto,
|
||||
},
|
||||
}
|
||||
|
||||
/// The unresolved length representation.
|
||||
///
|
||||
/// Currently supports absolute and em units, but support could quite easily be
|
||||
/// extended to other units that can be resolved through a style chain.
|
||||
/// Probably, it would be a good idea to then move to an enum representation
|
||||
/// that has a small footprint and allocates for the rare case that units are
|
||||
/// mixed.
|
||||
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct RawLength {
|
||||
/// The absolute part.
|
||||
pub length: Length,
|
||||
/// The font-relative part.
|
||||
pub em: Em,
|
||||
}
|
||||
|
||||
impl RawLength {
|
||||
/// The zero length.
|
||||
pub const fn zero() -> Self {
|
||||
Self { length: Length::zero(), em: Em::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RawLength {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match (self.length.is_zero(), self.em.is_zero()) {
|
||||
(false, false) => write!(f, "{:?} + {:?}", self.length, self.em),
|
||||
(true, false) => self.em.fmt(f),
|
||||
(_, true) => self.length.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for Em {
|
||||
type Output = Length;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
if self.is_zero() {
|
||||
Length::zero()
|
||||
} else {
|
||||
self.at(styles.get(TextNode::SIZE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for RawLength {
|
||||
type Output = Length;
|
||||
|
||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||
self.length + self.em.resolve(styles)
|
||||
}
|
||||
}
|
||||
|
||||
impl Numeric for RawLength {
|
||||
fn zero() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
|
||||
fn is_finite(self) -> bool {
|
||||
self.length.is_finite() && self.em.is_finite()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for RawLength {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
if self.em.is_zero() && other.em.is_zero() {
|
||||
self.length.partial_cmp(&other.length)
|
||||
} else if self.length.is_zero() && other.length.is_zero() {
|
||||
self.em.partial_cmp(&other.em)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for RawLength {
|
||||
fn from(length: Length) -> Self {
|
||||
Self { length, em: Em::zero() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Em> for RawLength {
|
||||
fn from(em: Em) -> Self {
|
||||
Self { length: Length::zero(), em }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for Relative<RawLength> {
|
||||
fn from(length: Length) -> Self {
|
||||
Relative::from(RawLength::from(length))
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for RawLength {
|
||||
type Output = Self;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
Self { length: -self.length, em: -self.em }
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for RawLength {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self {
|
||||
length: self.length + rhs.length,
|
||||
em: self.em + rhs.em,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub_impl!(RawLength - RawLength -> RawLength);
|
||||
|
||||
impl Mul<f64> for RawLength {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: f64) -> Self::Output {
|
||||
Self {
|
||||
length: self.length * rhs,
|
||||
em: self.em * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for RawLength {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: f64) -> Self::Output {
|
||||
Self {
|
||||
length: self.length / rhs,
|
||||
em: self.em / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assign_impl!(RawLength += RawLength);
|
||||
assign_impl!(RawLength -= RawLength);
|
||||
assign_impl!(RawLength *= f64);
|
||||
assign_impl!(RawLength /= f64);
|
||||
|
@ -7,9 +7,9 @@ use std::sync::Arc;
|
||||
use comemo::Tracked;
|
||||
use siphasher::sip128::{Hasher128, SipHasher};
|
||||
|
||||
use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, RawLength, Str};
|
||||
use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str};
|
||||
use crate::diag::StrResult;
|
||||
use crate::geom::{Angle, Color, Em, Fraction, Length, Ratio, Relative, RgbaColor};
|
||||
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
|
||||
use crate::util::EcoString;
|
||||
use crate::World;
|
||||
|
||||
@ -26,16 +26,16 @@ pub enum Value {
|
||||
Int(i64),
|
||||
/// A floating-point number: `1.2`, `10e-4`.
|
||||
Float(f64),
|
||||
/// A length: `12pt`, `3cm`, `1.5em`.
|
||||
Length(RawLength),
|
||||
/// A length: `12pt`, `3cm`, `1.5em`, `1em - 2pt`.
|
||||
Length(Length),
|
||||
/// An angle: `1.5rad`, `90deg`.
|
||||
Angle(Angle),
|
||||
/// A ratio: `50%`.
|
||||
Ratio(Ratio),
|
||||
/// A relative length, combination of a ratio and a length: `20% + 5cm`.
|
||||
Relative(Relative<RawLength>),
|
||||
Relative(Rel<Length>),
|
||||
/// A fraction: `1fr`.
|
||||
Fraction(Fraction),
|
||||
Fraction(Fr),
|
||||
/// A color value: `#f79143ff`.
|
||||
Color(Color),
|
||||
/// A string: `"string"`.
|
||||
@ -87,11 +87,11 @@ impl Value {
|
||||
Self::Bool(_) => bool::TYPE_NAME,
|
||||
Self::Int(_) => i64::TYPE_NAME,
|
||||
Self::Float(_) => f64::TYPE_NAME,
|
||||
Self::Length(_) => RawLength::TYPE_NAME,
|
||||
Self::Length(_) => Length::TYPE_NAME,
|
||||
Self::Angle(_) => Angle::TYPE_NAME,
|
||||
Self::Ratio(_) => Ratio::TYPE_NAME,
|
||||
Self::Relative(_) => Relative::<RawLength>::TYPE_NAME,
|
||||
Self::Fraction(_) => Fraction::TYPE_NAME,
|
||||
Self::Relative(_) => Rel::<Length>::TYPE_NAME,
|
||||
Self::Fraction(_) => Fr::TYPE_NAME,
|
||||
Self::Color(_) => Color::TYPE_NAME,
|
||||
Self::Str(_) => Str::TYPE_NAME,
|
||||
Self::Content(_) => Content::TYPE_NAME,
|
||||
@ -210,8 +210,8 @@ impl From<usize> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Length> for Value {
|
||||
fn from(v: Length) -> Self {
|
||||
impl From<Abs> for Value {
|
||||
fn from(v: Abs) -> Self {
|
||||
Self::Length(v.into())
|
||||
}
|
||||
}
|
||||
@ -385,15 +385,15 @@ macro_rules! primitive {
|
||||
primitive! { bool: "boolean", Bool }
|
||||
primitive! { i64: "integer", Int }
|
||||
primitive! { f64: "float", Float, Int(v) => v as f64 }
|
||||
primitive! { RawLength: "length", Length }
|
||||
primitive! { Length: "length", Length }
|
||||
primitive! { Angle: "angle", Angle }
|
||||
primitive! { Ratio: "ratio", Ratio }
|
||||
primitive! { Relative<RawLength>: "relative length",
|
||||
primitive! { Rel<Length>: "relative length",
|
||||
Relative,
|
||||
Length(v) => v.into(),
|
||||
Ratio(v) => v.into()
|
||||
}
|
||||
primitive! { Fraction: "fraction", Fraction }
|
||||
primitive! { Fr: "fraction", Fraction }
|
||||
primitive! { Color: "color", Color }
|
||||
primitive! { Str: "string", Str }
|
||||
primitive! { Content: "content",
|
||||
@ -422,14 +422,14 @@ mod tests {
|
||||
test(false, "false");
|
||||
test(12i64, "12");
|
||||
test(3.14, "3.14");
|
||||
test(Length::pt(5.5), "5.5pt");
|
||||
test(Abs::pt(5.5), "5.5pt");
|
||||
test(Angle::deg(90.0), "90deg");
|
||||
test(Ratio::one() / 2.0, "50%");
|
||||
test(
|
||||
Ratio::new(0.3) + RawLength::from(Length::cm(2.0)),
|
||||
Ratio::new(0.3) + Length::from(Abs::cm(2.0)),
|
||||
"30% + 56.69pt",
|
||||
);
|
||||
test(Fraction::one() * 7.55, "7.55fr");
|
||||
test(Fr::one() * 7.55, "7.55fr");
|
||||
test(
|
||||
Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)),
|
||||
"rgb(\"#010101\")",
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::geom::{AngleUnit, LengthUnit};
|
||||
use crate::geom::{AbsUnit, AngleUnit};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// All syntactical building blocks that can be part of a Typst document.
|
||||
@ -277,7 +277,7 @@ pub struct RawKind {
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Unit {
|
||||
/// An absolute length unit.
|
||||
Length(LengthUnit),
|
||||
Length(AbsUnit),
|
||||
/// An angular unit.
|
||||
Angle(AngleUnit),
|
||||
/// Font-relative: `1em` is the same as the font size.
|
||||
|
@ -5,7 +5,7 @@ use unscanny::Scanner;
|
||||
|
||||
use super::resolve::{resolve_hex, resolve_raw, resolve_string};
|
||||
use super::{ErrorPos, NodeKind, RawKind, Unit};
|
||||
use crate::geom::{AngleUnit, LengthUnit};
|
||||
use crate::geom::{AbsUnit, AngleUnit};
|
||||
use crate::util::EcoString;
|
||||
|
||||
/// An iterator over the tokens of a string of source code.
|
||||
@ -565,10 +565,10 @@ impl<'s> Tokens<'s> {
|
||||
|
||||
match suffix {
|
||||
"" => NodeKind::Float(v),
|
||||
"pt" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Pt)),
|
||||
"mm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Mm)),
|
||||
"cm" => NodeKind::Numeric(v, Unit::Length(LengthUnit::Cm)),
|
||||
"in" => NodeKind::Numeric(v, Unit::Length(LengthUnit::In)),
|
||||
"pt" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Pt)),
|
||||
"mm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Mm)),
|
||||
"cm" => NodeKind::Numeric(v, Unit::Length(AbsUnit::Cm)),
|
||||
"in" => NodeKind::Numeric(v, Unit::Length(AbsUnit::In)),
|
||||
"deg" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Deg)),
|
||||
"rad" => NodeKind::Numeric(v, Unit::Angle(AngleUnit::Rad)),
|
||||
"em" => NodeKind::Numeric(v, Unit::Em),
|
||||
@ -1089,10 +1089,10 @@ mod tests {
|
||||
let nums = ints.iter().map(|&(k, v)| (k, v as f64)).chain(floats);
|
||||
|
||||
let suffixes: &[(&str, fn(f64) -> NodeKind)] = &[
|
||||
("mm", |x| Numeric(x, Unit::Length(LengthUnit::Mm))),
|
||||
("pt", |x| Numeric(x, Unit::Length(LengthUnit::Pt))),
|
||||
("cm", |x| Numeric(x, Unit::Length(LengthUnit::Cm))),
|
||||
("in", |x| Numeric(x, Unit::Length(LengthUnit::In))),
|
||||
("mm", |x| Numeric(x, Unit::Length(AbsUnit::Mm))),
|
||||
("pt", |x| Numeric(x, Unit::Length(AbsUnit::Pt))),
|
||||
("cm", |x| Numeric(x, Unit::Length(AbsUnit::Cm))),
|
||||
("in", |x| Numeric(x, Unit::Length(AbsUnit::In))),
|
||||
("rad", |x| Numeric(x, Unit::Angle(AngleUnit::Rad))),
|
||||
("deg", |x| Numeric(x, Unit::Angle(AngleUnit::Deg))),
|
||||
("em", |x| Numeric(x, Unit::Em)),
|
||||
|
@ -17,7 +17,7 @@ use walkdir::WalkDir;
|
||||
use typst::diag::{FileError, FileResult};
|
||||
use typst::font::{Font, FontBook};
|
||||
use typst::frame::{Element, Frame};
|
||||
use typst::geom::{Length, RgbaColor, Sides};
|
||||
use typst::geom::{Abs, RgbaColor, Sides};
|
||||
use typst::library::layout::PageNode;
|
||||
use typst::library::text::{TextNode, TextSize};
|
||||
use typst::model::{Smart, StyleMap, Value};
|
||||
@ -150,13 +150,13 @@ fn config() -> Config {
|
||||
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
|
||||
// that it multiplies to nice round numbers.
|
||||
let mut styles = StyleMap::new();
|
||||
styles.set(PageNode::WIDTH, Smart::Custom(Length::pt(120.0).into()));
|
||||
styles.set(PageNode::WIDTH, Smart::Custom(Abs::pt(120.0).into()));
|
||||
styles.set(PageNode::HEIGHT, Smart::Auto);
|
||||
styles.set(
|
||||
PageNode::MARGINS,
|
||||
Sides::splat(Some(Smart::Custom(Length::pt(10.0).into()))),
|
||||
Sides::splat(Some(Smart::Custom(Abs::pt(10.0).into()))),
|
||||
);
|
||||
styles.set(TextNode::SIZE, TextSize(Length::pt(10.0).into()));
|
||||
styles.set(TextNode::SIZE, TextSize(Abs::pt(10.0).into()));
|
||||
|
||||
// Hook up helpers into the global scope.
|
||||
let mut std = typst::library::scope();
|
||||
@ -653,7 +653,7 @@ fn render(frames: &[Frame]) -> sk::Pixmap {
|
||||
let pixmaps: Vec<_> = frames
|
||||
.iter()
|
||||
.map(|frame| {
|
||||
let limit = Length::cm(100.0);
|
||||
let limit = Abs::cm(100.0);
|
||||
if frame.width() > limit || frame.height() > limit {
|
||||
panic!("overlarge frame: {:?}", frame.size());
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user