use crate::derives::*;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::resolution::Resolution as ComputedResolution;
use crate::values::computed::{Context, ToComputedValue};
use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
use crate::values::tagged_numeric::{NumericUnion, Unpacked};
use crate::values::CSSFloat;
use cssparser::{match_ignore_ascii_case, Parser, Token};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
#[repr(u8)]
pub enum ResolutionUnit {
Dpi,
X,
Dppx,
Dpcm,
}
impl ResolutionUnit {
#[inline]
pub fn as_str(self) -> &'static str {
match self {
Self::Dpi => "dpi",
Self::X => "x",
Self::Dppx => "dppx",
Self::Dpcm => "dpcm",
}
}
}
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[repr(C)]
pub struct NoCalcResolution {
unit: ResolutionUnit,
value: CSSFloat,
}
impl NoCalcResolution {
#[inline]
pub fn new(unit: ResolutionUnit, value: CSSFloat) -> Self {
Self { unit, value }
}
#[inline]
pub fn from_dppx(value: CSSFloat) -> Self {
Self::new(ResolutionUnit::Dppx, value)
}
#[inline]
pub fn from_x(value: CSSFloat) -> Self {
Self::new(ResolutionUnit::X, value)
}
pub fn dppx(&self) -> CSSFloat {
match self.unit {
ResolutionUnit::X | ResolutionUnit::Dppx => self.value,
_ => self.dpi() / 96.0,
}
}
pub fn dpi(&self) -> CSSFloat {
match self.unit {
ResolutionUnit::Dpi => self.value,
ResolutionUnit::X | ResolutionUnit::Dppx => self.value * 96.0,
ResolutionUnit::Dpcm => self.value * 2.54,
}
}
#[inline]
pub fn resolution_unit(&self) -> ResolutionUnit {
self.unit
}
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
let unit = match_ignore_ascii_case! { &unit,
"dpi" => ResolutionUnit::Dpi,
"dppx" => ResolutionUnit::Dppx,
"dpcm" => ResolutionUnit::Dpcm,
"x" => ResolutionUnit::X,
_ => return Err(())
};
Ok(Self::new(unit, value))
}
}
impl ToCss for NoCalcResolution {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
crate::values::serialize_specified_dimension(
self.value,
self.unit.as_str(),
false,
dest,
)
}
}
impl SpecifiedValueInfo for NoCalcResolution {}
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct Resolution(NumericUnion<ResolutionUnit, f32, CalcNumeric>);
impl ToCss for Resolution {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
match self.0.unpack() {
Unpacked::Inline(unit, value) => NoCalcResolution::new(unit, value).to_css(dest),
Unpacked::Boxed(calc) => calc.to_css(dest),
}
}
}
impl SpecifiedValueInfo for Resolution {}
impl Resolution {
#[inline]
pub fn new(resolution: NoCalcResolution) -> Self {
Self(NumericUnion::inline(resolution.unit, resolution.value))
}
#[inline]
pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
Self(NumericUnion::boxed(calc))
}
#[inline]
pub fn from_dppx(value: CSSFloat) -> Self {
Self::new(NoCalcResolution::from_dppx(value))
}
#[inline]
pub fn from_x(value: CSSFloat) -> Self {
Self::new(NoCalcResolution::from_x(value))
}
#[inline]
pub fn is_calc(&self) -> bool {
self.0.is_boxed()
}
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
NoCalcResolution::parse_dimension(value, unit).map(Self::new)
}
}
impl ToComputedValue for Resolution {
type ComputedValue = ComputedResolution;
#[inline]
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
let dppx = match self.0.unpack() {
Unpacked::Inline(unit, value) => NoCalcResolution::new(unit, value).dppx().max(0.0),
Unpacked::Boxed(calc) => calc.resolve(context, |result| match result {
Ok(Leaf::Resolution(r)) => r.dppx().max(0.0),
_ => {
debug_assert!(
false,
"Unexpected Resolution::Calc without resolved resolution"
);
0.0
},
}),
};
ComputedResolution::from_dppx(crate::values::normalize(dppx))
}
#[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self {
Self::from_dppx(computed.dppx())
}
}
impl Parse for Resolution {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
match *input.next()? {
Token::Dimension {
value, ref unit, ..
} if value >= 0. => Self::parse_dimension(value, unit)
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
Token::Function(ref name) => {
let function = CalcNode::math_function(context, name, location)?;
CalcNode::parse_resolution(context, input, function)
.map(Box::new)
.map(Self::new_calc)
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
}
}
}