use crate::style_vocab::Length;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LengthSpec {
Pt(f64),
Relative(f64),
Em(f64),
Rem(f64),
}
impl LengthSpec {
#[inline]
pub fn resolve(self, parent_pt: f64, own_size_pt: f64, base_size_pt: f64) -> f64 {
match self {
LengthSpec::Pt(v) => v,
LengthSpec::Relative(m) => parent_pt * m,
LengthSpec::Em(m) => own_size_pt * m,
LengthSpec::Rem(m) => base_size_pt * m,
}
}
#[inline]
pub fn is_absolute(self) -> bool {
matches!(self, LengthSpec::Pt(_))
}
}
impl Default for LengthSpec {
fn default() -> Self {
LengthSpec::Relative(1.0)
}
}
impl From<Length> for LengthSpec {
fn from(l: Length) -> Self {
match l {
Length::Abs(v) => LengthSpec::Pt(v),
Length::Rel(m) => LengthSpec::Relative(m),
}
}
}
#[inline]
pub const fn pt(v: f64) -> LengthSpec {
LengthSpec::Pt(v)
}
#[inline]
pub const fn relative(m: f64) -> LengthSpec {
LengthSpec::Relative(m)
}
#[inline]
pub const fn em(m: f64) -> LengthSpec {
LengthSpec::Em(m)
}
#[inline]
pub const fn rem(m: f64) -> LengthSpec {
LengthSpec::Rem(m)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RichMargin {
pub top: LengthSpec,
pub right: LengthSpec,
pub bottom: LengthSpec,
pub left: LengthSpec,
}
impl RichMargin {
#[inline]
pub const fn all(v: LengthSpec) -> Self {
Self {
top: v,
right: v,
bottom: v,
left: v,
}
}
#[inline]
pub const fn new(
top: LengthSpec,
right: LengthSpec,
bottom: LengthSpec,
left: LengthSpec,
) -> Self {
Self {
top,
right,
bottom,
left,
}
}
#[inline]
pub fn resolve(&self, parent: [f64; 4], own_size_pt: f64, base_size_pt: f64) -> [f64; 4] {
[
self.top.resolve(parent[0], own_size_pt, base_size_pt),
self.right.resolve(parent[1], own_size_pt, base_size_pt),
self.bottom.resolve(parent[2], own_size_pt, base_size_pt),
self.left.resolve(parent[3], own_size_pt, base_size_pt),
]
}
pub const ZERO: RichMargin = RichMargin::all(LengthSpec::Pt(0.0));
}
impl Default for RichMargin {
fn default() -> Self {
Self::ZERO
}
}
#[inline]
pub fn swap_lr(sides: [f64; 4], is_rtl: bool) -> [f64; 4] {
if is_rtl {
[sides[0], sides[3], sides[2], sides[1]]
} else {
sides
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineHeightSpec {
Mult(f64),
Relative(f64),
Pt(f64),
}
impl LineHeightSpec {
#[inline]
pub fn resolve(self, parent: LineHeightSpec) -> LineHeightSpec {
match self {
LineHeightSpec::Relative(m) => match parent {
LineHeightSpec::Mult(p) => LineHeightSpec::Mult(p * m),
LineHeightSpec::Relative(p) => LineHeightSpec::Mult(p * m),
LineHeightSpec::Pt(p) => LineHeightSpec::Pt(p * m),
},
other => other,
}
}
}
impl Default for LineHeightSpec {
fn default() -> Self {
LineHeightSpec::Mult(1.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StyleField {
Family,
Weight,
Italic,
Width,
Size,
Color,
Tracking,
Underline,
Strikethrough,
Baseline,
TextStroke,
TextStrokeWidth,
LineHeight,
Align,
Indent,
Hanging,
Margin,
Padding,
Background,
BorderColor,
BorderWidth,
BorderRadius,
Bullet,
}
impl StyleField {
pub const ALL: [StyleField; 23] = [
StyleField::Family,
StyleField::Weight,
StyleField::Italic,
StyleField::Width,
StyleField::Size,
StyleField::Color,
StyleField::Tracking,
StyleField::Underline,
StyleField::Strikethrough,
StyleField::Baseline,
StyleField::TextStroke,
StyleField::TextStrokeWidth,
StyleField::LineHeight,
StyleField::Align,
StyleField::Indent,
StyleField::Hanging,
StyleField::Margin,
StyleField::Padding,
StyleField::Background,
StyleField::BorderColor,
StyleField::BorderWidth,
StyleField::BorderRadius,
StyleField::Bullet,
];
#[inline]
fn bit(self) -> u32 {
self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FieldSet(u32);
impl FieldSet {
pub const NONE: FieldSet = FieldSet(0);
pub fn of(fields: &[StyleField]) -> Self {
let mut bits = 0u32;
for f in fields {
bits |= 1 << f.bit();
}
FieldSet(bits)
}
#[inline]
pub fn contains(self, field: StyleField) -> bool {
self.0 & (1 << field.bit()) != 0
}
#[inline]
pub fn is_empty(self) -> bool {
self.0 == 0
}
#[inline]
pub fn union(self, other: FieldSet) -> FieldSet {
FieldSet(self.0 | other.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn each_variant_measures_against_its_own_anchor() {
assert_eq!(pt(7.0).resolve(20.0, 30.0, 10.0), 7.0);
assert_eq!(relative(2.0).resolve(20.0, 30.0, 10.0), 40.0);
assert_eq!(em(2.0).resolve(20.0, 30.0, 10.0), 60.0);
assert_eq!(rem(2.0).resolve(20.0, 30.0, 10.0), 20.0);
}
#[test]
fn margin_resolves_each_side_against_the_matching_parent_side() {
let m = RichMargin::new(relative(2.0), pt(1.0), em(0.5), rem(0.25));
let out = m.resolve([3.0, 4.0, 5.0, 6.0], 10.0, 8.0);
assert_eq!(out, [6.0, 1.0, 5.0, 2.0]);
}
#[test]
fn swap_lr_flips_only_under_rtl() {
assert_eq!(swap_lr([1.0, 2.0, 3.0, 4.0], false), [1.0, 2.0, 3.0, 4.0]);
assert_eq!(swap_lr([1.0, 2.0, 3.0, 4.0], true), [1.0, 4.0, 3.0, 2.0]);
}
#[test]
fn relative_lineheight_compounds_within_the_parent_kind() {
assert_eq!(
LineHeightSpec::Relative(0.5).resolve(LineHeightSpec::Mult(1.6)),
LineHeightSpec::Mult(0.8)
);
assert_eq!(
LineHeightSpec::Relative(0.5).resolve(LineHeightSpec::Pt(20.0)),
LineHeightSpec::Pt(10.0)
);
assert_eq!(
LineHeightSpec::Mult(2.0).resolve(LineHeightSpec::Pt(20.0)),
LineHeightSpec::Mult(2.0)
);
}
#[test]
fn field_set_membership_is_per_field() {
let s = FieldSet::of(&[StyleField::Size, StyleField::Baseline]);
assert!(s.contains(StyleField::Size));
assert!(s.contains(StyleField::Baseline));
assert!(!s.contains(StyleField::Margin));
assert!(FieldSet::NONE.is_empty());
for f in StyleField::ALL {
let only = FieldSet::of(&[f]);
for g in StyleField::ALL {
assert_eq!(only.contains(g), f == g, "{f:?} vs {g:?}");
}
}
}
}