Add hash impls for all nodes

This prepares the incremental PR.

Co-Authored-By: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Martin Haug 2021-05-26 22:52:02 +02:00 committed by Laurenz
parent 14f093bfee
commit e27f6c1014
28 changed files with 198 additions and 145 deletions

View File

@ -21,6 +21,8 @@ debug = 0
opt-level = 2 opt-level = 2
[dependencies] [dependencies]
decorum = { version = "0.3.1", default-features = false, features = ["serialize-serde"] }
fxhash = "0.2.1"
image = { version = "0.23", default-features = false, features = ["jpeg", "png"] } image = { version = "0.23", default-features = false, features = ["jpeg", "png"] }
miniz_oxide = "0.3" miniz_oxide = "0.3"
pdf-writer = { path = "../pdf-writer" } pdf-writer = { path = "../pdf-writer" }

View File

@ -6,7 +6,7 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A color in a dynamic format. /// A color in a dynamic format.
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Color { pub enum Color {
/// An 8-bit RGBA color: `#423abaff`. /// An 8-bit RGBA color: `#423abaff`.
Rgba(RgbaColor), Rgba(RgbaColor),
@ -29,7 +29,7 @@ impl Debug for Color {
} }
/// An 8-bit RGBA color: `#423abaff`. /// An 8-bit RGBA color: `#423abaff`.
#[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct RgbaColor { pub struct RgbaColor {
/// Red channel. /// Red channel.
pub r: u8, pub r: u8,

View File

@ -184,7 +184,7 @@ impl Default for FontState {
size: Length::pt(11.0), size: Length::pt(11.0),
top_edge: VerticalFontMetric::CapHeight, top_edge: VerticalFontMetric::CapHeight,
bottom_edge: VerticalFontMetric::Baseline, bottom_edge: VerticalFontMetric::Baseline,
scale: Linear::ONE, scale: Linear::one(),
color: Fill::Color(Color::Rgba(RgbaColor::BLACK)), color: Fill::Color(Color::Rgba(RgbaColor::BLACK)),
strong: false, strong: false,
emph: false, emph: false,
@ -193,7 +193,7 @@ impl Default for FontState {
} }
/// Properties used for font selection and layout. /// Properties used for font selection and layout.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct FontProps { pub struct FontProps {
/// The list of font families to use for shaping. /// The list of font families to use for shaping.
pub families: Rc<FamilyList>, pub families: Rc<FamilyList>,
@ -210,7 +210,7 @@ pub struct FontProps {
} }
/// Font family definitions. /// Font family definitions.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FamilyList { pub struct FamilyList {
/// The user-defined list of font families. /// The user-defined list of font families.
pub list: Vec<FontFamily>, pub list: Vec<FontFamily>,
@ -255,7 +255,7 @@ impl Default for FamilyList {
} }
/// A generic or named font family. /// A generic or named font family.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FontFamily { pub enum FontFamily {
Serif, Serif,
SansSerif, SansSerif,

View File

@ -92,7 +92,7 @@ impl Face {
} }
/// Identifies a vertical metric of a font. /// Identifies a vertical metric of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VerticalFontMetric { pub enum VerticalFontMetric {
/// The distance from the baseline to the typographic ascender. /// The distance from the baseline to the typographic ascender.
/// ///
@ -169,7 +169,7 @@ pub struct FaceInfo {
} }
/// Properties that distinguish a face from other faces in the same family. /// Properties that distinguish a face from other faces in the same family.
#[derive(Default, Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Copy, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub struct FontVariant { pub struct FontVariant {
/// The style of the face (normal / italic / oblique). /// The style of the face (normal / italic / oblique).
pub style: FontStyle, pub style: FontStyle,
@ -187,7 +187,7 @@ impl FontVariant {
} }
/// The style of a font face. /// The style of a font face.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum FontStyle { pub enum FontStyle {
@ -233,7 +233,8 @@ impl Display for FontStyle {
} }
/// The weight of a font face. /// The weight of a font face.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct FontWeight(u16); pub struct FontWeight(u16);
@ -353,42 +354,43 @@ impl Debug for FontWeight {
} }
/// The width of a font face. /// The width of a font face.
#[derive(Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct FontStretch(f32); pub struct FontStretch(u16);
impl FontStretch { impl FontStretch {
/// Ultra-condensed stretch (50%). /// Ultra-condensed stretch (50%).
pub const ULTRA_CONDENSED: Self = Self(0.5); pub const ULTRA_CONDENSED: Self = Self(500);
/// Extra-condensed stretch weight (62.5%). /// Extra-condensed stretch weight (62.5%).
pub const EXTRA_CONDENSED: Self = Self(0.625); pub const EXTRA_CONDENSED: Self = Self(625);
/// Condensed stretch (75%). /// Condensed stretch (75%).
pub const CONDENSED: Self = Self(0.75); pub const CONDENSED: Self = Self(750);
/// Semi-condensed stretch (87.5%). /// Semi-condensed stretch (87.5%).
pub const SEMI_CONDENSED: Self = Self(0.875); pub const SEMI_CONDENSED: Self = Self(875);
/// Normal stretch (100%). /// Normal stretch (100%).
pub const NORMAL: Self = Self(1.0); pub const NORMAL: Self = Self(1000);
/// Semi-expanded stretch (112.5%). /// Semi-expanded stretch (112.5%).
pub const SEMI_EXPANDED: Self = Self(1.125); pub const SEMI_EXPANDED: Self = Self(1125);
/// Expanded stretch (125%). /// Expanded stretch (125%).
pub const EXPANDED: Self = Self(1.25); pub const EXPANDED: Self = Self(1250);
/// Extra-expanded stretch (150%). /// Extra-expanded stretch (150%).
pub const EXTRA_EXPANDED: Self = Self(1.5); pub const EXTRA_EXPANDED: Self = Self(1500);
/// Ultra-expanded stretch (200%). /// Ultra-expanded stretch (200%).
pub const ULTRA_EXPANDED: Self = Self(2.0); pub const ULTRA_EXPANDED: Self = Self(2000);
/// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if /// Create a font stretch from a ratio between 0.5 and 2.0, clamping it if
/// necessary. /// necessary.
pub fn from_ratio(ratio: f32) -> Self { pub fn from_ratio(ratio: f32) -> Self {
Self(ratio.max(0.5).min(2.0)) Self((ratio.max(0.5).min(2.0) * 1000.0) as u16)
} }
/// Create a font stretch from an OpenType-style number between 1 and 9, /// Create a font stretch from an OpenType-style number between 1 and 9,
@ -425,29 +427,29 @@ impl FontStretch {
/// The ratio between 0.5 and 2.0 corresponding to this stretch. /// The ratio between 0.5 and 2.0 corresponding to this stretch.
pub fn to_ratio(self) -> f32 { pub fn to_ratio(self) -> f32 {
self.0 self.0 as f32 / 1000.0
} }
/// The lowercase string representation of this stretch is one of the named /// The lowercase string representation of this stretch is one of the named
/// ones. /// ones.
pub fn to_str(self) -> Option<&'static str> { pub fn to_str(self) -> Option<&'static str> {
Some(match self { Some(match self {
s if s == Self::ULTRA_CONDENSED => "ultra-condensed", Self::ULTRA_CONDENSED => "ultra-condensed",
s if s == Self::EXTRA_CONDENSED => "extra-condensed", Self::EXTRA_CONDENSED => "extra-condensed",
s if s == Self::CONDENSED => "condensed", Self::CONDENSED => "condensed",
s if s == Self::SEMI_CONDENSED => "semi-condensed", Self::SEMI_CONDENSED => "semi-condensed",
s if s == Self::NORMAL => "normal", Self::NORMAL => "normal",
s if s == Self::SEMI_EXPANDED => "semi-expanded", Self::SEMI_EXPANDED => "semi-expanded",
s if s == Self::EXPANDED => "expanded", Self::EXPANDED => "expanded",
s if s == Self::EXTRA_EXPANDED => "extra-expanded", Self::EXTRA_EXPANDED => "extra-expanded",
s if s == Self::ULTRA_EXPANDED => "ultra-expanded", Self::ULTRA_EXPANDED => "ultra-expanded",
_ => return None, _ => return None,
}) })
} }
/// The absolute ratio distance between this and another font stretch. /// The absolute ratio distance between this and another font stretch.
pub fn distance(self, other: Self) -> f32 { pub fn distance(self, other: Self) -> f32 {
(self.0 - other.0).abs() (self.to_ratio() - other.to_ratio()).abs()
} }
} }
@ -461,7 +463,7 @@ impl Display for FontStretch {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self.to_str() { match self.to_str() {
Some(name) => f.pad(name), Some(name) => f.pad(name),
None => write!(f, "{}", self.0), None => write!(f, "{}", self.to_ratio()),
} }
} }
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// Where to align something along a directed axis. /// Where to align something along a directed axis.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Align { pub enum Align {
/// Align at the start of the axis. /// Align at the start of the axis.
Start, Start,

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// The four directions into which content can be laid out. /// The four directions into which content can be laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Dir { pub enum Dir {
/// Left to right. /// Left to right.
LTR, LTR,

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A container with a main and cross component. /// A container with a main and cross component.
#[derive(Default, Copy, Clone, Eq, PartialEq)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Gen<T> { pub struct Gen<T> {
/// The cross component. /// The cross component.
pub cross: T, pub cross: T,
@ -26,7 +26,12 @@ impl<T> Gen<T> {
impl Gen<Length> { impl Gen<Length> {
/// The zero value. /// The zero value.
pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO }; pub fn zero() -> Self {
Self {
main: Length::zero(),
cross: Length::zero(),
}
}
} }
impl<T> Get<GenAxis> for Gen<T> { impl<T> Get<GenAxis> for Gen<T> {

View File

@ -1,18 +1,22 @@
use super::*; use decorum::NotNan;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::*;
/// An absolute length. /// An absolute length.
#[derive(Default, Copy, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd, Hash)]
#[derive(Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct Length { pub struct Length {
/// The length in raw units. /// The length in raw units.
raw: f64, raw: NotNan<f64>,
} }
impl Length { impl Length {
/// The zero length. /// The zero length.
pub const ZERO: Self = Self { raw: 0.0 }; pub fn zero() -> Self {
Self { raw: 0.0.into() }
}
/// Create a length from a number of points. /// Create a length from a number of points.
pub fn pt(pt: f64) -> Self { pub fn pt(pt: f64) -> Self {
@ -35,8 +39,8 @@ impl Length {
} }
/// Create a length from a number of raw units. /// Create a length from a number of raw units.
pub const fn raw(raw: f64) -> Self { pub fn raw(raw: f64) -> Self {
Self { raw } Self { raw: raw.into() }
} }
/// Convert this to a number of points. /// Convert this to a number of points.
@ -60,18 +64,18 @@ impl Length {
} }
/// Get the value of this length in raw units. /// Get the value of this length in raw units.
pub const fn to_raw(self) -> f64 { pub fn to_raw(self) -> f64 {
self.raw self.raw.into()
} }
/// Create a length from a value in a unit. /// Create a length from a value in a unit.
pub fn with_unit(val: f64, unit: LengthUnit) -> Self { pub fn with_unit(val: f64, unit: LengthUnit) -> Self {
Self { raw: val * unit.raw_scale() } Self { raw: (val * unit.raw_scale()).into() }
} }
/// Get the value of this length in unit. /// Get the value of this length in unit.
pub fn to_unit(self, unit: LengthUnit) -> f64 { pub fn to_unit(self, unit: LengthUnit) -> f64 {
self.raw / unit.raw_scale() self.to_raw() / unit.raw_scale()
} }
/// The minimum of this and another length. /// The minimum of this and another length.
@ -106,17 +110,12 @@ impl Length {
/// Whether the length is finite. /// Whether the length is finite.
pub fn is_finite(self) -> bool { pub fn is_finite(self) -> bool {
self.raw.is_finite() self.raw.into_inner().is_finite()
} }
/// Whether the length is infinite. /// Whether the length is infinite.
pub fn is_infinite(self) -> bool { pub fn is_infinite(self) -> bool {
self.raw.is_infinite() self.raw.into_inner().is_infinite()
}
/// Whether the length is `NaN`.
pub fn is_nan(self) -> bool {
self.raw.is_nan()
} }
} }
@ -189,7 +188,7 @@ impl Div for Length {
type Output = f64; type Output = f64;
fn div(self, other: Self) -> f64 { fn div(self, other: Self) -> f64 {
self.raw / other.raw self.to_raw() / other.to_raw()
} }
} }
@ -200,7 +199,7 @@ assign_impl!(Length /= f64);
impl Sum for Length { impl Sum for Length {
fn sum<I: Iterator<Item = Length>>(iter: I) -> Self { fn sum<I: Iterator<Item = Length>>(iter: I) -> Self {
iter.fold(Length::ZERO, Add::add) iter.fold(Length::zero(), Add::add)
} }
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A combined relative and absolute length. /// A combined relative and absolute length.
#[derive(Default, Copy, Clone, PartialEq)] #[derive(Default, Copy, Clone, PartialEq, Hash)]
pub struct Linear { pub struct Linear {
/// The relative part. /// The relative part.
pub rel: Relative, pub rel: Relative,
@ -11,10 +11,20 @@ pub struct Linear {
impl Linear { impl Linear {
/// The zero linear. /// The zero linear.
pub const ZERO: Self = Self { rel: Relative::ZERO, abs: Length::ZERO }; pub fn zero() -> Self {
Self {
rel: Relative::zero(),
abs: Length::zero(),
}
}
/// The linear with a relative part of `100%` and no absolute part. /// The linear with a relative part of `100%` and no absolute part.
pub const ONE: Self = Self { rel: Relative::ONE, abs: Length::ZERO }; pub fn one() -> Self {
Self {
rel: Relative::one(),
abs: Length::zero(),
}
}
/// Create a new linear. /// Create a new linear.
pub fn new(rel: Relative, abs: Length) -> Self { pub fn new(rel: Relative, abs: Length) -> Self {
@ -46,13 +56,13 @@ impl Debug for Linear {
impl From<Length> for Linear { impl From<Length> for Linear {
fn from(abs: Length) -> Self { fn from(abs: Length) -> Self {
Self { rel: Relative::ZERO, abs } Self { rel: Relative::zero(), abs }
} }
} }
impl From<Relative> for Linear { impl From<Relative> for Linear {
fn from(rel: Relative) -> Self { fn from(rel: Relative) -> Self {
Self { rel, abs: Length::ZERO } Self { rel, abs: Length::zero() }
} }
} }

View File

@ -30,7 +30,7 @@ impl Path {
let m = 0.551784; let m = 0.551784;
let mx = m * rx; let mx = m * rx;
let my = m * ry; let my = m * ry;
let z = Length::ZERO; let z = Length::zero();
let point = Point::new; let point = Point::new;
let mut path = Self::new(); let mut path = Self::new();
path.move_to(point(-rx, z)); path.move_to(point(-rx, z));

View File

@ -13,7 +13,9 @@ pub struct Point {
impl Point { impl Point {
/// The origin point. /// The origin point.
pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO }; pub fn zero() -> Self {
Self { x: Length::zero(), y: Length::zero() }
}
/// Create a new point from x and y coordinate. /// Create a new point from x and y coordinate.
pub fn new(x: Length, y: Length) -> Self { pub fn new(x: Length, y: Length) -> Self {

View File

@ -1,34 +1,40 @@
use decorum::NotNan;
use super::*; use super::*;
/// A relative length. /// A relative length.
/// ///
/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the /// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the
/// corresponding [literal](crate::syntax::Expr::Percent). /// corresponding [literal](crate::syntax::Expr::Percent).
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] #[derive(Default, Copy, Clone, PartialEq, PartialOrd, Hash)]
pub struct Relative(f64); pub struct Relative(NotNan<f64>);
impl Relative { impl Relative {
/// A ratio of `0%` represented as `0.0`. /// A ratio of `0%` represented as `0.0`.
pub const ZERO: Self = Self(0.0); pub fn zero() -> Self {
Self(0.0.into())
}
/// A ratio of `100%` represented as `1.0`. /// A ratio of `100%` represented as `1.0`.
pub const ONE: Self = Self(1.0); pub fn one() -> Self {
Self(1.0.into())
}
/// Create a new relative value. /// Create a new relative value.
pub fn new(ratio: f64) -> Self { pub fn new(ratio: f64) -> Self {
Self(ratio) Self(ratio.into())
} }
/// Get the underlying ratio. /// Get the underlying ratio.
pub fn get(self) -> f64 { pub fn get(self) -> f64 {
self.0 self.0.into()
} }
/// Resolve this relative to the given `length`. /// Resolve this relative to the given `length`.
pub fn resolve(self, length: Length) -> Length { pub fn resolve(self, length: Length) -> Length {
// We don't want NaNs. // We don't want NaNs.
if length.is_infinite() { if length.is_infinite() {
Length::ZERO Length::zero()
} else { } else {
self.get() * length self.get() * length
} }
@ -42,7 +48,7 @@ impl Relative {
impl Display for Relative { impl Display for Relative {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}%", 100.0 * self.0) write!(f, "{}%", 100.0 * self.get())
} }
} }
@ -98,7 +104,7 @@ impl Div for Relative {
type Output = f64; type Output = f64;
fn div(self, other: Self) -> f64 { fn div(self, other: Self) -> f64 {
self.0 / other.0 self.get() / other.get()
} }
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A container with left, top, right and bottom components. /// A container with left, top, right and bottom components.
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
pub struct Sides<T> { pub struct Sides<T> {
/// The value for the left side. /// The value for the left side.
pub left: T, pub left: T,

View File

@ -13,10 +13,12 @@ pub struct Size {
impl Size { impl Size {
/// The zero size. /// The zero size.
pub const ZERO: Self = Self { pub fn zero() -> Self {
width: Length::ZERO, Self {
height: Length::ZERO, width: Length::zero(),
}; height: Length::zero(),
}
}
/// Create a new size from width and height. /// Create a new size from width and height.
pub fn new(width: Length, height: Length) -> Self { pub fn new(width: Length, height: Length) -> Self {
@ -43,11 +45,6 @@ impl Size {
self.width.is_infinite() || self.height.is_infinite() self.width.is_infinite() || self.height.is_infinite()
} }
/// Whether any of the two components is `NaN`.
pub fn is_nan(self) -> bool {
self.width.is_nan() || self.height.is_nan()
}
/// Convert to a point. /// Convert to a point.
pub fn to_point(self) -> Point { pub fn to_point(self) -> Point {
Point::new(self.width, self.height) Point::new(self.width, self.height)

View File

@ -29,10 +29,12 @@ impl<T> Spec<T> {
impl Spec<Length> { impl Spec<Length> {
/// The zero value. /// The zero value.
pub const ZERO: Self = Self { pub fn zero() -> Self {
horizontal: Length::ZERO, Self {
vertical: Length::ZERO, horizontal: Length::zero(),
}; vertical: Length::zero(),
}
}
/// Convert to a point. /// Convert to a point.
pub fn to_point(self) -> Point { pub fn to_point(self) -> Point {

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A node that places a rectangular filled background behind its child. /// A node that places a rectangular filled background behind its child.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct BackgroundNode { pub struct BackgroundNode {
/// The kind of shape to use as a background. /// The kind of shape to use as a background.
pub shape: BackgroundShape, pub shape: BackgroundShape,
@ -12,7 +12,7 @@ pub struct BackgroundNode {
} }
/// The kind of shape to use as a background. /// The kind of shape to use as a background.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum BackgroundShape { pub enum BackgroundShape {
Rect, Rect,
Ellipse, Ellipse,
@ -24,7 +24,7 @@ impl Layout for BackgroundNode {
for frame in &mut frames { for frame in &mut frames {
let (point, shape) = match self.shape { let (point, shape) = match self.shape {
BackgroundShape::Rect => (Point::ZERO, Shape::Rect(frame.size)), BackgroundShape::Rect => (Point::zero(), Shape::Rect(frame.size)),
BackgroundShape::Ellipse => { BackgroundShape::Ellipse => {
(frame.size.to_point() / 2.0, Shape::Ellipse(frame.size)) (frame.size.to_point() / 2.0, Shape::Ellipse(frame.size))
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A node that can fix its child's width and height. /// A node that can fix its child's width and height.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct FixedNode { pub struct FixedNode {
/// The fixed width, if any. /// The fixed width, if any.
pub width: Option<Linear>, pub width: Option<Linear>,

View File

@ -96,7 +96,7 @@ pub enum Shape {
} }
/// How text and shapes are filled. /// How text and shapes are filled.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Hash, Serialize, Deserialize)]
pub enum Fill { pub enum Fill {
/// A solid color. /// A solid color.
Color(Color), Color(Color),

View File

@ -18,6 +18,10 @@ pub use stack::*;
use std::any::Any; use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher};
use decorum::NotNan;
use fxhash::FxHasher64;
use crate::env::Env; use crate::env::Env;
use crate::geom::*; use crate::geom::*;
@ -64,39 +68,62 @@ impl PageRun {
} }
/// A wrapper around a dynamic layouting node. /// A wrapper around a dynamic layouting node.
pub struct AnyNode(Box<dyn Bounds>); pub struct AnyNode {
node: Box<dyn Bounds>,
hash: u64,
}
impl AnyNode { impl AnyNode {
/// Create a new instance from any node that satisifies the required bounds. /// Create a new instance from any node that satisifies the required bounds.
pub fn new<T>(any: T) -> Self pub fn new<T>(node: T) -> Self
where where
T: Layout + Debug + Clone + PartialEq + 'static, T: Layout + Debug + Clone + PartialEq + Hash + 'static,
{ {
Self(Box::new(any)) let hash = {
let mut state = FxHasher64::default();
node.hash(&mut state);
state.finish()
};
Self { node: Box::new(node), hash }
}
/// The cached hash for the boxed node.
pub fn hash(&self) -> u64 {
self.hash
} }
} }
impl Layout for AnyNode { impl Layout for AnyNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
self.0.layout(ctx, regions) self.node.layout(ctx, regions)
} }
} }
impl Clone for AnyNode { impl Clone for AnyNode {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.dyn_clone()) Self {
node: self.node.dyn_clone(),
hash: self.hash,
}
} }
} }
impl PartialEq for AnyNode { impl PartialEq for AnyNode {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.0.dyn_eq(other.0.as_ref()) self.node.dyn_eq(other.node.as_ref())
}
}
impl Hash for AnyNode {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
} }
} }
impl Debug for AnyNode { impl Debug for AnyNode {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
self.0.fmt(f) self.node.fmt(f)
} }
} }
@ -202,10 +229,7 @@ impl Regions {
/// ///
/// If this is true, calling `next()` will have no effect. /// If this is true, calling `next()` will have no effect.
pub fn in_full_last(&self) -> bool { pub fn in_full_last(&self) -> bool {
self.backlog.is_empty() self.backlog.is_empty() && self.last.map_or(true, |size| self.current == size)
&& self.last.map_or(true, |size| {
self.current.is_nan() || size.is_nan() || self.current == size
})
} }
/// Advance to the next region if there is any. /// Advance to the next region if there is any.
@ -217,9 +241,9 @@ impl Regions {
} }
/// Shrink `current` to ensure that the aspect ratio can be satisfied. /// Shrink `current` to ensure that the aspect ratio can be satisfied.
pub fn apply_aspect_ratio(&mut self, aspect: f64) { pub fn apply_aspect_ratio(&mut self, aspect: NotNan<f64>) {
let width = self.current.width.min(aspect * self.current.height); let width = self.current.width.min(aspect.into_inner() * self.current.height);
let height = width / aspect; let height = width / aspect.into_inner();
self.current = Size::new(width, height); self.current = Size::new(width, height);
} }
} }

View File

@ -1,7 +1,7 @@
use super::*; use super::*;
/// A node that adds padding to its child. /// A node that adds padding to its child.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct PadNode { pub struct PadNode {
/// The amount of padding. /// The amount of padding.
pub padding: Sides<Linear>, pub padding: Sides<Linear>,

View File

@ -10,7 +10,7 @@ use crate::util::{RangeExt, SliceExt};
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;
/// A node that arranges its children into a paragraph. /// A node that arranges its children into a paragraph.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct ParNode { pub struct ParNode {
/// The inline direction of this paragraph. /// The inline direction of this paragraph.
pub dir: Dir, pub dir: Dir,
@ -21,7 +21,7 @@ pub struct ParNode {
} }
/// A child of a paragraph node. /// A child of a paragraph node.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq, Hash)]
pub enum ParChild { pub enum ParChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),
@ -255,7 +255,7 @@ impl ParItem<'_> {
/// The size of the item. /// The size of the item.
pub fn size(&self) -> Size { pub fn size(&self) -> Size {
match self { match self {
Self::Spacing(amount) => Size::new(*amount, Length::ZERO), Self::Spacing(amount) => Size::new(*amount, Length::zero()),
Self::Text(shaped, _) => shaped.size, Self::Text(shaped, _) => shaped.size,
Self::Frame(frame, _) => frame.size, Self::Frame(frame, _) => frame.size,
} }
@ -264,7 +264,7 @@ impl ParItem<'_> {
/// The baseline of the item. /// The baseline of the item.
pub fn baseline(&self) -> Length { pub fn baseline(&self) -> Length {
match self { match self {
Self::Spacing(_) => Length::ZERO, Self::Spacing(_) => Length::zero(),
Self::Text(shaped, _) => shaped.baseline, Self::Text(shaped, _) => shaped.baseline,
Self::Frame(frame, _) => frame.baseline, Self::Frame(frame, _) => frame.baseline,
} }
@ -287,7 +287,7 @@ impl<'a> LineStack<'a> {
regions, regions,
finished: vec![], finished: vec![],
lines: vec![], lines: vec![],
size: Size::ZERO, size: Size::zero(),
} }
} }
@ -308,13 +308,13 @@ impl<'a> LineStack<'a> {
} }
let mut output = Frame::new(self.size, self.size.height); let mut output = Frame::new(self.size, self.size.height);
let mut offset = Length::ZERO; let mut offset = Length::zero();
let mut first = true; let mut first = true;
for line in std::mem::take(&mut self.lines) { for line in std::mem::take(&mut self.lines) {
let frame = line.build(self.size.width); let frame = line.build(self.size.width);
let pos = Point::new(Length::ZERO, offset); let pos = Point::new(Length::zero(), offset);
if first { if first {
output.baseline = pos.y + frame.baseline; output.baseline = pos.y + frame.baseline;
first = false; first = false;
@ -326,7 +326,7 @@ impl<'a> LineStack<'a> {
self.finished.push(output); self.finished.push(output);
self.regions.next(); self.regions.next();
self.size = Size::ZERO; self.size = Size::zero();
} }
fn finish(mut self) -> Vec<Frame> { fn finish(mut self) -> Vec<Frame> {
@ -421,9 +421,9 @@ impl<'a> LineLayout<'a> {
} }
} }
let mut width = Length::ZERO; let mut width = Length::zero();
let mut top = Length::ZERO; let mut top = Length::zero();
let mut bottom = Length::ZERO; let mut bottom = Length::zero();
// Measure the size of the line. // Measure the size of the line.
for item in first.iter().chain(items).chain(&last) { for item in first.iter().chain(items).chain(&last) {
@ -452,7 +452,7 @@ impl<'a> LineLayout<'a> {
let free = size.width - self.size.width; let free = size.width - self.size.width;
let mut output = Frame::new(size, self.baseline); let mut output = Frame::new(size, self.baseline);
let mut offset = Length::ZERO; let mut offset = Length::zero();
let mut ruler = Align::Start; let mut ruler = Align::Start;
self.reordered(|item| { self.reordered(|item| {

View File

@ -62,7 +62,7 @@ impl<'a> ShapedText<'a> {
/// Build the shaped text's frame. /// Build the shaped text's frame.
pub fn build(&self) -> Frame { pub fn build(&self) -> Frame {
let mut frame = Frame::new(self.size, self.baseline); let mut frame = Frame::new(self.size, self.baseline);
let mut offset = Length::ZERO; let mut offset = Length::zero();
for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) {
let pos = Point::new(offset, self.baseline); let pos = Point::new(offset, self.baseline);
@ -331,9 +331,9 @@ fn measure(
glyphs: &[ShapedGlyph], glyphs: &[ShapedGlyph],
props: &FontProps, props: &FontProps,
) -> (Size, Length) { ) -> (Size, Length) {
let mut width = Length::ZERO; let mut width = Length::zero();
let mut top = Length::ZERO; let mut top = Length::zero();
let mut bottom = Length::ZERO; let mut bottom = Length::zero();
let mut expand_vertical = |face: &Face| { let mut expand_vertical = |face: &Face| {
top.set_max(face.vertical_metric(props.top_edge).to_length(props.size)); top.set_max(face.vertical_metric(props.top_edge).to_length(props.size));
bottom.set_max(-face.vertical_metric(props.bottom_edge).to_length(props.size)); bottom.set_max(-face.vertical_metric(props.bottom_edge).to_length(props.size));

View File

@ -1,7 +1,9 @@
use decorum::NotNan;
use super::*; use super::*;
/// A node that stacks its children. /// A node that stacks its children.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub struct StackNode { pub struct StackNode {
/// The `main` and `cross` directions of this stack. /// The `main` and `cross` directions of this stack.
/// ///
@ -11,13 +13,13 @@ pub struct StackNode {
/// The fixed aspect ratio between width and height, if any. /// The fixed aspect ratio between width and height, if any.
/// ///
/// The resulting frames will satisfy `width = aspect * height`. /// The resulting frames will satisfy `width = aspect * height`.
pub aspect: Option<f64>, pub aspect: Option<NotNan<f64>>,
/// The nodes to be stacked. /// The nodes to be stacked.
pub children: Vec<StackChild>, pub children: Vec<StackChild>,
} }
/// A child of a stack node. /// A child of a stack node.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum StackChild { pub enum StackChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),
@ -56,7 +58,7 @@ impl From<StackNode> for AnyNode {
struct StackLayouter { struct StackLayouter {
dirs: Gen<Dir>, dirs: Gen<Dir>,
aspect: Option<f64>, aspect: Option<NotNan<f64>>,
main: SpecAxis, main: SpecAxis,
regions: Regions, regions: Regions,
finished: Vec<Frame>, finished: Vec<Frame>,
@ -67,7 +69,7 @@ struct StackLayouter {
} }
impl StackLayouter { impl StackLayouter {
fn new(dirs: Gen<Dir>, aspect: Option<f64>, mut regions: Regions) -> Self { fn new(dirs: Gen<Dir>, aspect: Option<NotNan<f64>>, mut regions: Regions) -> Self {
if let Some(aspect) = aspect { if let Some(aspect) = aspect {
regions.apply_aspect_ratio(aspect); regions.apply_aspect_ratio(aspect);
} }
@ -79,7 +81,7 @@ impl StackLayouter {
finished: vec![], finished: vec![],
frames: vec![], frames: vec![],
full: regions.current, full: regions.current,
size: Gen::ZERO, size: Gen::zero(),
ruler: Align::Start, ruler: Align::Start,
regions, regions,
} }
@ -122,11 +124,11 @@ impl StackLayouter {
if let Some(aspect) = self.aspect { if let Some(aspect) = self.aspect {
let width = size let width = size
.width .width
.max(aspect * size.height) .max(aspect.into_inner() * size.height)
.min(self.full.width) .min(self.full.width)
.min(aspect * self.full.height); .min(aspect.into_inner() * self.full.height);
size = Size::new(width, width / aspect); size = Size::new(width, width / aspect.into_inner());
} }
let mut output = Frame::new(size, size.height); let mut output = Frame::new(size, size.height);
@ -141,7 +143,7 @@ impl StackLayouter {
// Align along the cross axis. // Align along the cross axis.
let cross = aligns let cross = aligns
.cross .cross
.resolve(self.dirs.cross, Length::ZERO .. size.cross - child.cross); .resolve(self.dirs.cross, Length::zero() .. size.cross - child.cross);
// Align along the main axis. // Align along the main axis.
let main = aligns.main.resolve( let main = aligns.main.resolve(
@ -163,7 +165,7 @@ impl StackLayouter {
output.push_frame(pos, frame); output.push_frame(pos, frame);
} }
self.size = Gen::ZERO; self.size = Gen::zero();
self.ruler = Align::Start; self.ruler = Align::Start;
self.regions.next(); self.regions.next();
if let Some(aspect) = self.aspect { if let Some(aspect) = self.aspect {

View File

@ -68,7 +68,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
if let Some(linear) = size { if let Some(linear) = size {
if linear.rel.is_zero() { if linear.rel.is_zero() {
ctx.state.font.size = linear.abs; ctx.state.font.size = linear.abs;
ctx.state.font.scale = Relative::ONE.into(); ctx.state.font.scale = Linear::one();
} else { } else {
ctx.state.font.scale = linear; ctx.state.font.scale = linear;
} }

View File

@ -32,7 +32,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
} }
/// An image node. /// An image node.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Hash)]
struct ImageNode { struct ImageNode {
/// The id of the image file. /// The id of the image file.
id: ImageId, id: ImageId,
@ -73,7 +73,7 @@ impl Layout for ImageNode {
}; };
let mut frame = Frame::new(size, size.height); let mut frame = Frame::new(size, size.height);
frame.push(Point::ZERO, Element::Image(self.id, size)); frame.push(Point::zero(), Element::Image(self.id, size));
vec![frame] vec![frame]
} }
} }

View File

@ -1,5 +1,7 @@
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use decorum::NotNan;
use super::*; use super::*;
use crate::color::Color; use crate::color::Color;
use crate::layout::{BackgroundNode, BackgroundShape, Fill, FixedNode, PadNode}; use crate::layout::{BackgroundNode, BackgroundShape, Fill, FixedNode, PadNode};
@ -47,14 +49,14 @@ pub fn square(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten(); let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten();
let fill = args.eat_named(ctx, "fill"); let fill = args.eat_named(ctx, "fill");
let body = args.eat::<TemplateValue>(ctx).unwrap_or_default(); let body = args.eat::<TemplateValue>(ctx).unwrap_or_default();
rect_impl("square", width, height, Some(1.0), fill, body) rect_impl("square", width, height, Some(1.0.into()), fill, body)
} }
fn rect_impl( fn rect_impl(
name: &str, name: &str,
width: Option<Linear>, width: Option<Linear>,
height: Option<Linear>, height: Option<Linear>,
aspect: Option<f64>, aspect: Option<NotNan<f64>>,
fill: Option<Color>, fill: Option<Color>,
body: TemplateValue, body: TemplateValue,
) -> Value { ) -> Value {
@ -119,14 +121,14 @@ pub fn circle(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten(); let height = width.is_none().then(|| args.eat_named(ctx, "height")).flatten();
let fill = args.eat_named(ctx, "fill"); let fill = args.eat_named(ctx, "fill");
let body = args.eat::<TemplateValue>(ctx).unwrap_or_default(); let body = args.eat::<TemplateValue>(ctx).unwrap_or_default();
ellipse_impl("circle", width, height, Some(1.0), fill, body) ellipse_impl("circle", width, height, Some(1.0.into()), fill, body)
} }
fn ellipse_impl( fn ellipse_impl(
name: &str, name: &str,
width: Option<Linear>, width: Option<Linear>,
height: Option<Linear>, height: Option<Linear>,
aspect: Option<f64>, aspect: Option<NotNan<f64>>,
fill: Option<Color>, fill: Option<Color>,
body: TemplateValue, body: TemplateValue,
) -> Value { ) -> Value {

View File

@ -132,7 +132,7 @@ impl<'a> PdfExporter<'a> {
// We only write font switching actions when the used face changes. To // We only write font switching actions when the used face changes. To
// do that, we need to remember the active face. // do that, we need to remember the active face.
let mut face = FaceId::MAX; let mut face = FaceId::MAX;
let mut size = Length::ZERO; let mut size = Length::zero();
let mut fill: Option<Fill> = None; let mut fill: Option<Fill> = None;
for (pos, element) in &page.elements { for (pos, element) in &page.elements {

View File

@ -742,7 +742,7 @@ mod tests {
test_value(3.14, "3.14"); test_value(3.14, "3.14");
test_value(Length::pt(5.5), "5.5pt"); test_value(Length::pt(5.5), "5.5pt");
test_value(Angle::deg(90.0), "90deg"); test_value(Angle::deg(90.0), "90deg");
test_value(Relative::ONE / 2.0, "50%"); test_value(Relative::one() / 2.0, "50%");
test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm"); test_value(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm");
test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_value(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101");
test_value("hello", r#""hello""#); test_value("hello", r#""hello""#);