use core::fmt;
use std::num::ParseFloatError;
use crate::props::{
basic::{FloatValue, SizeMetric},
formatter::FormatAsCssValue,
};
pub const DEFAULT_FONT_SIZE: f32 = 16.0;
pub const PT_TO_PX: f32 = 96.0 / 72.0;
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct NormalizedPercentage(f32);
impl NormalizedPercentage {
#[inline]
pub const fn new(value: f32) -> Self {
Self(value)
}
#[inline]
pub fn from_unnormalized(value: f32) -> Self {
Self(value / 100.0)
}
#[inline]
pub const fn get(self) -> f32 {
self.0
}
#[inline]
pub fn resolve(self, containing_block_size: f32) -> f32 {
self.0 * containing_block_size
}
}
impl fmt::Display for NormalizedPercentage {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}%", self.0 * 100.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct CssLogicalSize {
pub inline_size: f32,
pub block_size: f32,
}
impl CssLogicalSize {
#[inline]
pub const fn new(inline_size: f32, block_size: f32) -> Self {
Self {
inline_size,
block_size,
}
}
#[inline]
pub const fn to_physical(self) -> PhysicalSize {
PhysicalSize {
width: self.inline_size,
height: self.block_size,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct PhysicalSize {
pub width: f32,
pub height: f32,
}
impl PhysicalSize {
#[inline]
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
#[inline]
pub const fn to_logical(self) -> CssLogicalSize {
CssLogicalSize {
inline_size: self.width,
block_size: self.height,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct ResolutionContext {
pub element_font_size: f32,
pub parent_font_size: f32,
pub root_font_size: f32,
pub containing_block_size: PhysicalSize,
pub element_size: Option<PhysicalSize>,
pub viewport_size: PhysicalSize,
}
impl Default for ResolutionContext {
fn default() -> Self {
Self {
element_font_size: 16.0,
parent_font_size: 16.0,
root_font_size: 16.0,
containing_block_size: PhysicalSize::new(0.0, 0.0),
element_size: None,
viewport_size: PhysicalSize::new(0.0, 0.0),
}
}
}
impl ResolutionContext {
#[inline]
pub const fn default_const() -> Self {
Self {
element_font_size: 16.0,
parent_font_size: 16.0,
root_font_size: 16.0,
containing_block_size: PhysicalSize {
width: 0.0,
height: 0.0,
},
element_size: None,
viewport_size: PhysicalSize {
width: 0.0,
height: 0.0,
},
}
}
#[inline]
pub const fn for_fonts(
element_font_size: f32,
parent_font_size: f32,
root_font_size: f32,
) -> Self {
Self {
element_font_size,
parent_font_size,
root_font_size,
containing_block_size: PhysicalSize {
width: 0.0,
height: 0.0,
},
element_size: None,
viewport_size: PhysicalSize {
width: 0.0,
height: 0.0,
},
}
}
#[inline]
pub const fn with_containing_block(mut self, containing_block_size: PhysicalSize) -> Self {
self.containing_block_size = containing_block_size;
self
}
#[inline]
pub const fn with_element_size(mut self, element_size: PhysicalSize) -> Self {
self.element_size = Some(element_size);
self
}
#[inline]
pub const fn with_viewport_size(mut self, viewport_size: PhysicalSize) -> Self {
self.viewport_size = viewport_size;
self
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PropertyContext {
FontSize,
Margin,
Padding,
Width,
Height,
BorderWidth,
BorderRadius,
Transform,
Other,
}
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct PixelValue {
pub metric: SizeMetric,
pub number: FloatValue,
}
impl PixelValue {
pub fn scale_for_dpi(&mut self, scale_factor: f32) {
self.number = FloatValue::new(self.number.get() * scale_factor);
}
}
impl FormatAsCssValue for PixelValue {
fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.number, self.metric)
}
}
impl crate::css::PrintAsCssValue for PixelValue {
fn print_as_css_value(&self) -> String {
format!("{}{}", self.number, self.metric)
}
}
impl crate::format_rust_code::FormatAsRustCode for PixelValue {
fn format_as_rust_code(&self, _tabs: usize) -> String {
format!(
"PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
self.metric,
self.number.get()
)
}
}
impl fmt::Debug for PixelValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.number, self.metric)
}
}
impl fmt::Display for PixelValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.number, self.metric)
}
}
impl PixelValue {
#[inline]
pub const fn zero() -> Self {
const ZERO_PX: PixelValue = PixelValue::const_px(0);
ZERO_PX
}
#[inline]
pub const fn const_px(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Px, value)
}
#[inline]
pub const fn const_em(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Em, value)
}
#[inline]
pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
}
#[inline]
pub const fn const_pt(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Pt, value)
}
#[inline]
pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
}
#[inline]
pub const fn const_percent(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Percent, value)
}
#[inline]
pub const fn const_in(value: isize) -> Self {
Self::const_from_metric(SizeMetric::In, value)
}
#[inline]
pub const fn const_cm(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Cm, value)
}
#[inline]
pub const fn const_mm(value: isize) -> Self {
Self::const_from_metric(SizeMetric::Mm, value)
}
#[inline]
pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
Self {
metric,
number: FloatValue::const_new(value),
}
}
#[inline]
pub const fn const_from_metric_fractional(
metric: SizeMetric,
pre_comma: isize,
post_comma: isize,
) -> Self {
Self {
metric,
number: FloatValue::const_new_fractional(pre_comma, post_comma),
}
}
#[inline]
pub fn px(value: f32) -> Self {
Self::from_metric(SizeMetric::Px, value)
}
#[inline]
pub fn em(value: f32) -> Self {
Self::from_metric(SizeMetric::Em, value)
}
#[inline]
pub fn inch(value: f32) -> Self {
Self::from_metric(SizeMetric::In, value)
}
#[inline]
pub fn cm(value: f32) -> Self {
Self::from_metric(SizeMetric::Cm, value)
}
#[inline]
pub fn mm(value: f32) -> Self {
Self::from_metric(SizeMetric::Mm, value)
}
#[inline]
pub fn pt(value: f32) -> Self {
Self::from_metric(SizeMetric::Pt, value)
}
#[inline]
pub fn percent(value: f32) -> Self {
Self::from_metric(SizeMetric::Percent, value)
}
#[inline]
pub fn rem(value: f32) -> Self {
Self::from_metric(SizeMetric::Rem, value)
}
#[inline]
pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
Self {
metric,
number: FloatValue::new(value),
}
}
#[inline]
pub fn interpolate(&self, other: &Self, t: f32) -> Self {
if self.metric == other.metric {
Self {
metric: self.metric,
number: self.number.interpolate(&other.number, t),
}
} else {
let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE);
let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE);
Self::from_metric(
SizeMetric::Px,
self_px_interp + (other_px_interp - self_px_interp) * t,
)
}
}
#[inline]
pub fn to_percent(&self) -> Option<NormalizedPercentage> {
match self.metric {
SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
_ => None,
}
}
#[doc(hidden)]
#[inline]
pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32) -> f32 {
match self.metric {
SizeMetric::Px => self.number.get(),
SizeMetric::Pt => self.number.get() * PT_TO_PX,
SizeMetric::In => self.number.get() * 96.0,
SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
SizeMetric::Em => self.number.get() * em_resolve,
SizeMetric::Rem => self.number.get() * em_resolve,
SizeMetric::Percent => {
NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
}
SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
}
}
#[inline]
pub fn resolve_with_context(
&self,
context: &ResolutionContext,
property_context: PropertyContext,
) -> f32 {
match self.metric {
SizeMetric::Px => self.number.get(),
SizeMetric::Pt => self.number.get() * PT_TO_PX,
SizeMetric::In => self.number.get() * 96.0,
SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
SizeMetric::Em => {
let reference_font_size = if property_context == PropertyContext::FontSize {
context.parent_font_size
} else {
context.element_font_size
};
self.number.get() * reference_font_size
}
SizeMetric::Rem => self.number.get() * context.root_font_size,
SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
SizeMetric::Vmin => {
let min_dimension = context
.viewport_size
.width
.min(context.viewport_size.height);
self.number.get() * min_dimension / 100.0
}
SizeMetric::Vmax => {
let max_dimension = context
.viewport_size
.width
.max(context.viewport_size.height);
self.number.get() * max_dimension / 100.0
}
SizeMetric::Percent => {
let reference = match property_context {
PropertyContext::FontSize => context.parent_font_size,
PropertyContext::Width => context.containing_block_size.width,
PropertyContext::Height => context.containing_block_size.height,
PropertyContext::Margin | PropertyContext::Padding => {
context.containing_block_size.width
}
PropertyContext::BorderWidth => 0.0,
PropertyContext::BorderRadius => {
context.element_size.map(|s| s.width).unwrap_or(0.0)
}
PropertyContext::Transform => {
context.element_size.map(|s| s.width).unwrap_or(0.0)
}
PropertyContext::Other => context.containing_block_size.width,
};
NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
}
}
}
}
pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
metric: SizeMetric::Px,
number: FloatValue { number: 1000 },
};
pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
metric: SizeMetric::Px,
number: FloatValue { number: 3000 },
};
pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
metric: SizeMetric::Px,
number: FloatValue { number: 5000 },
};
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct PixelValueNoPercent {
pub inner: PixelValue,
}
impl PixelValueNoPercent {
pub fn scale_for_dpi(&mut self, scale_factor: f32) {
self.inner.scale_for_dpi(scale_factor);
}
}
impl_option!(
PixelValueNoPercent,
OptionPixelValueNoPercent,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_option!(
PixelValue,
OptionPixelValue,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl fmt::Display for PixelValueNoPercent {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.inner)
}
}
impl ::core::fmt::Debug for PixelValueNoPercent {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "{}", self)
}
}
impl PixelValueNoPercent {
#[doc(hidden)]
#[inline]
pub fn to_pixels_internal(&self, em_resolve: f32) -> f32 {
self.inner.to_pixels_internal(0.0, em_resolve)
}
#[inline]
pub const fn zero() -> Self {
const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
inner: PixelValue::zero(),
};
ZERO_PXNP
}
}
impl From<PixelValue> for PixelValueNoPercent {
fn from(e: PixelValue) -> Self {
Self { inner: e }
}
}
#[derive(Clone, PartialEq)]
pub enum CssPixelValueParseError<'a> {
EmptyString,
NoValueGiven(&'a str, SizeMetric),
ValueParseErr(ParseFloatError, &'a str),
InvalidPixelValue(&'a str),
}
impl_debug_as_display!(CssPixelValueParseError<'a>);
impl_display! { CssPixelValueParseError<'a>, {
EmptyString => format!("Missing [px / pt / em / %] value"),
NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
}}
#[derive(Debug, Clone, PartialEq)]
pub enum CssPixelValueParseErrorOwned {
EmptyString,
NoValueGiven(String, SizeMetric),
ValueParseErr(ParseFloatError, String),
InvalidPixelValue(String),
}
impl<'a> CssPixelValueParseError<'a> {
pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
match self {
CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
CssPixelValueParseError::NoValueGiven(s, metric) => {
CssPixelValueParseErrorOwned::NoValueGiven(s.to_string(), *metric)
}
CssPixelValueParseError::ValueParseErr(err, s) => {
CssPixelValueParseErrorOwned::ValueParseErr(err.clone(), s.to_string())
}
CssPixelValueParseError::InvalidPixelValue(s) => {
CssPixelValueParseErrorOwned::InvalidPixelValue(s.to_string())
}
}
}
}
impl CssPixelValueParseErrorOwned {
pub fn to_shared<'a>(&'a self) -> CssPixelValueParseError<'a> {
match self {
CssPixelValueParseErrorOwned::EmptyString => CssPixelValueParseError::EmptyString,
CssPixelValueParseErrorOwned::NoValueGiven(s, metric) => {
CssPixelValueParseError::NoValueGiven(s.as_str(), *metric)
}
CssPixelValueParseErrorOwned::ValueParseErr(err, s) => {
CssPixelValueParseError::ValueParseErr(err.clone(), s.as_str())
}
CssPixelValueParseErrorOwned::InvalidPixelValue(s) => {
CssPixelValueParseError::InvalidPixelValue(s.as_str())
}
}
}
}
fn parse_pixel_value_inner<'a>(
input: &'a str,
match_values: &[(&'static str, SizeMetric)],
) -> Result<PixelValue, CssPixelValueParseError<'a>> {
let input = input.trim();
if input.is_empty() {
return Err(CssPixelValueParseError::EmptyString);
}
for (match_val, metric) in match_values {
if input.ends_with(match_val) {
let value = &input[..input.len() - match_val.len()];
let value = value.trim();
if value.is_empty() {
return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
}
match value.parse::<f32>() {
Ok(o) => {
return Ok(PixelValue::from_metric(*metric, o));
}
Err(e) => {
return Err(CssPixelValueParseError::ValueParseErr(e, value));
}
}
}
}
match input.trim().parse::<f32>() {
Ok(o) => Ok(PixelValue::px(o)),
Err(_) => Err(CssPixelValueParseError::InvalidPixelValue(input)),
}
}
pub fn parse_pixel_value<'a>(input: &'a str) -> Result<PixelValue, CssPixelValueParseError<'a>> {
parse_pixel_value_inner(
input,
&[
("px", SizeMetric::Px),
("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
("pt", SizeMetric::Pt),
("in", SizeMetric::In),
("mm", SizeMetric::Mm),
("cm", SizeMetric::Cm),
("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
("vh", SizeMetric::Vh),
("%", SizeMetric::Percent),
],
)
}
pub fn parse_pixel_value_no_percent<'a>(
input: &'a str,
) -> Result<PixelValueNoPercent, CssPixelValueParseError<'a>> {
Ok(PixelValueNoPercent {
inner: parse_pixel_value_inner(
input,
&[
("px", SizeMetric::Px),
("rem", SizeMetric::Rem), ("em", SizeMetric::Em),
("pt", SizeMetric::Pt),
("in", SizeMetric::In),
("mm", SizeMetric::Mm),
("cm", SizeMetric::Cm),
("vmax", SizeMetric::Vmax), ("vmin", SizeMetric::Vmin), ("vw", SizeMetric::Vw),
("vh", SizeMetric::Vh),
],
)?,
})
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PixelValueWithAuto {
None,
Initial,
Inherit,
Auto,
Exact(PixelValue),
}
pub fn parse_pixel_value_with_auto<'a>(
input: &'a str,
) -> Result<PixelValueWithAuto, CssPixelValueParseError<'a>> {
let input = input.trim();
match input {
"none" => Ok(PixelValueWithAuto::None),
"initial" => Ok(PixelValueWithAuto::Initial),
"inherit" => Ok(PixelValueWithAuto::Inherit),
"auto" => Ok(PixelValueWithAuto::Auto),
e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum SystemMetricRef {
ButtonRadius,
ButtonPaddingHorizontal,
ButtonPaddingVertical,
ButtonBorderWidth,
TitlebarHeight,
TitlebarButtonWidth,
TitlebarPadding,
SafeAreaTop,
SafeAreaBottom,
SafeAreaLeft,
SafeAreaRight,
}
impl Default for SystemMetricRef {
fn default() -> Self {
SystemMetricRef::ButtonRadius
}
}
impl SystemMetricRef {
pub fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
match self {
SystemMetricRef::ButtonRadius => metrics.corner_radius.as_option().copied(),
SystemMetricRef::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
SystemMetricRef::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
SystemMetricRef::ButtonBorderWidth => metrics.border_width.as_option().copied(),
SystemMetricRef::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
SystemMetricRef::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
SystemMetricRef::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
SystemMetricRef::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
SystemMetricRef::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
SystemMetricRef::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
SystemMetricRef::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
}
}
pub fn as_css_str(&self) -> &'static str {
match self {
SystemMetricRef::ButtonRadius => "system:button-radius",
SystemMetricRef::ButtonPaddingHorizontal => "system:button-padding-horizontal",
SystemMetricRef::ButtonPaddingVertical => "system:button-padding-vertical",
SystemMetricRef::ButtonBorderWidth => "system:button-border-width",
SystemMetricRef::TitlebarHeight => "system:titlebar-height",
SystemMetricRef::TitlebarButtonWidth => "system:titlebar-button-width",
SystemMetricRef::TitlebarPadding => "system:titlebar-padding",
SystemMetricRef::SafeAreaTop => "system:safe-area-top",
SystemMetricRef::SafeAreaBottom => "system:safe-area-bottom",
SystemMetricRef::SafeAreaLeft => "system:safe-area-left",
SystemMetricRef::SafeAreaRight => "system:safe-area-right",
}
}
pub fn from_css_str(s: &str) -> Option<Self> {
match s {
"button-radius" => Some(SystemMetricRef::ButtonRadius),
"button-padding-horizontal" => Some(SystemMetricRef::ButtonPaddingHorizontal),
"button-padding-vertical" => Some(SystemMetricRef::ButtonPaddingVertical),
"button-border-width" => Some(SystemMetricRef::ButtonBorderWidth),
"titlebar-height" => Some(SystemMetricRef::TitlebarHeight),
"titlebar-button-width" => Some(SystemMetricRef::TitlebarButtonWidth),
"titlebar-padding" => Some(SystemMetricRef::TitlebarPadding),
"safe-area-top" => Some(SystemMetricRef::SafeAreaTop),
"safe-area-bottom" => Some(SystemMetricRef::SafeAreaBottom),
"safe-area-left" => Some(SystemMetricRef::SafeAreaLeft),
"safe-area-right" => Some(SystemMetricRef::SafeAreaRight),
_ => None,
}
}
}
impl fmt::Display for SystemMetricRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_css_str())
}
}
impl FormatAsCssValue for SystemMetricRef {
fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_css_str())
}
}
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[repr(C, u8)]
pub enum PixelValueOrSystem {
Value(PixelValue),
System(SystemMetricRef),
}
impl Default for PixelValueOrSystem {
fn default() -> Self {
PixelValueOrSystem::Value(PixelValue::zero())
}
}
impl From<PixelValue> for PixelValueOrSystem {
fn from(value: PixelValue) -> Self {
PixelValueOrSystem::Value(value)
}
}
impl PixelValueOrSystem {
pub const fn value(v: PixelValue) -> Self {
PixelValueOrSystem::Value(v)
}
pub const fn system(s: SystemMetricRef) -> Self {
PixelValueOrSystem::System(s)
}
pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
match self {
PixelValueOrSystem::Value(v) => *v,
PixelValueOrSystem::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
}
}
pub fn to_pixel_value_with_fallback(&self, fallback: PixelValue) -> PixelValue {
match self {
PixelValueOrSystem::Value(v) => *v,
PixelValueOrSystem::System(_) => fallback,
}
}
pub fn to_pixel_value_default(&self) -> PixelValue {
self.to_pixel_value_with_fallback(PixelValue::zero())
}
}
impl fmt::Display for PixelValueOrSystem {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PixelValueOrSystem::Value(v) => write!(f, "{}", v),
PixelValueOrSystem::System(s) => write!(f, "{}", s),
}
}
}
impl FormatAsCssValue for PixelValueOrSystem {
fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PixelValueOrSystem::Value(v) => v.format_as_css_value(f),
PixelValueOrSystem::System(s) => s.format_as_css_value(f),
}
}
}
#[cfg(feature = "parser")]
pub fn parse_pixel_value_or_system<'a>(
input: &'a str,
) -> Result<PixelValueOrSystem, CssPixelValueParseError<'a>> {
let input = input.trim();
if let Some(metric_name) = input.strip_prefix("system:") {
if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
return Ok(PixelValueOrSystem::System(metric_ref));
} else {
return Err(CssPixelValueParseError::InvalidPixelValue(input));
}
}
Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
}
#[cfg(all(test, feature = "parser"))]
mod tests {
use super::*;
#[test]
fn test_parse_pixel_value() {
assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
assert_eq!(parse_pixel_value(" 0 ").unwrap(), PixelValue::px(0.0));
}
#[test]
fn test_resolve_with_context_em() {
let context = ResolutionContext {
element_font_size: 32.0,
parent_font_size: 16.0,
..Default::default()
};
let margin = PixelValue::em(0.67);
assert!(
(margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
);
let font_size = PixelValue::em(2.0);
assert_eq!(
font_size.resolve_with_context(&context, PropertyContext::FontSize),
32.0
);
}
#[test]
fn test_resolve_with_context_rem() {
let context = ResolutionContext {
element_font_size: 32.0,
parent_font_size: 16.0,
root_font_size: 18.0,
..Default::default()
};
let margin = PixelValue::rem(2.0);
assert_eq!(
margin.resolve_with_context(&context, PropertyContext::Margin),
36.0
);
let font_size = PixelValue::rem(1.5);
assert_eq!(
font_size.resolve_with_context(&context, PropertyContext::FontSize),
27.0
);
}
#[test]
fn test_resolve_with_context_percent_margin() {
let context = ResolutionContext {
element_font_size: 16.0,
parent_font_size: 16.0,
root_font_size: 16.0,
containing_block_size: PhysicalSize::new(800.0, 600.0),
element_size: None,
viewport_size: PhysicalSize::new(1920.0, 1080.0),
};
let margin = PixelValue::percent(10.0); assert_eq!(
margin.resolve_with_context(&context, PropertyContext::Margin),
80.0
); }
#[test]
fn test_parse_pixel_value_no_percent() {
assert_eq!(
parse_pixel_value_no_percent("10px").unwrap().inner,
PixelValue::px(10.0)
);
assert!(parse_pixel_value_no_percent("50%").is_err());
}
#[test]
fn test_parse_pixel_value_with_auto() {
assert_eq!(
parse_pixel_value_with_auto("10px").unwrap(),
PixelValueWithAuto::Exact(PixelValue::px(10.0))
);
assert_eq!(
parse_pixel_value_with_auto("auto").unwrap(),
PixelValueWithAuto::Auto
);
assert_eq!(
parse_pixel_value_with_auto("initial").unwrap(),
PixelValueWithAuto::Initial
);
assert_eq!(
parse_pixel_value_with_auto("inherit").unwrap(),
PixelValueWithAuto::Inherit
);
assert_eq!(
parse_pixel_value_with_auto("none").unwrap(),
PixelValueWithAuto::None
);
}
#[test]
fn test_parse_pixel_value_errors() {
assert!(parse_pixel_value("").is_err());
assert!(parse_pixel_value("10").is_ok()); assert!(parse_pixel_value("10 px").is_ok()); assert!(parse_pixel_value("px").is_err());
assert!(parse_pixel_value("ten-px").is_err());
}
}