Skip to main content

style/values/specified/
resolution.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Resolution values:
6//!
7//! https://drafts.csswg.org/css-values/#resolution
8
9use crate::derives::*;
10use crate::parser::{Parse, ParserContext};
11use crate::values::computed::resolution::Resolution as ComputedResolution;
12use crate::values::computed::{Context, ToComputedValue};
13use crate::values::specified::calc::{CalcNode, CalcNumeric, Leaf};
14use crate::values::tagged_numeric::{NumericUnion, Unpacked};
15use crate::values::CSSFloat;
16use cssparser::{match_ignore_ascii_case, Parser, Token};
17use std::fmt::{self, Write};
18use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
19
20/// The unit of a `<resolution>` value.
21#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
22#[repr(u8)]
23pub enum ResolutionUnit {
24    /// Dots per inch.
25    Dpi,
26    /// An alias unit for dots per pixel.
27    X,
28    /// Dots per pixel.
29    Dppx,
30    /// Dots per centimeter.
31    Dpcm,
32}
33
34impl ResolutionUnit {
35    /// Returns the resolution unit for the given string.
36    #[inline]
37    pub fn from_str(unit: &str) -> Result<Self, ()> {
38        Ok(match_ignore_ascii_case! { &unit,
39            "dpi" => Self::Dpi,
40            "dppx" => Self::Dppx,
41            "dpcm" => Self::Dpcm,
42            "x" => Self::X,
43            _ => return Err(())
44        })
45    }
46
47    /// Returns this unit as a string.
48    #[inline]
49    pub fn as_str(self) -> &'static str {
50        match self {
51            Self::Dpi => "dpi",
52            Self::X => "x",
53            Self::Dppx => "dppx",
54            Self::Dpcm => "dpcm",
55        }
56    }
57}
58
59/// A non-calc `<resolution>` value.
60#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
61#[repr(C)]
62pub struct NoCalcResolution {
63    unit: ResolutionUnit,
64    value: CSSFloat,
65}
66
67impl NoCalcResolution {
68    /// Creates a resolution with the given unit and value.
69    #[inline]
70    pub fn new(unit: ResolutionUnit, value: CSSFloat) -> Self {
71        Self { unit, value }
72    }
73
74    /// Returns a resolution value from dppx units.
75    #[inline]
76    pub fn from_dppx(value: CSSFloat) -> Self {
77        Self::new(ResolutionUnit::Dppx, value)
78    }
79
80    /// Returns a resolution value from x units.
81    #[inline]
82    pub fn from_x(value: CSSFloat) -> Self {
83        Self::new(ResolutionUnit::X, value)
84    }
85
86    /// Convert this resolution value to dppx units.
87    pub fn dppx(&self) -> CSSFloat {
88        match self.unit {
89            ResolutionUnit::X | ResolutionUnit::Dppx => self.value,
90            _ => self.dpi() / 96.0,
91        }
92    }
93
94    /// Convert this resolution value to dpi units.
95    pub fn dpi(&self) -> CSSFloat {
96        match self.unit {
97            ResolutionUnit::Dpi => self.value,
98            ResolutionUnit::X | ResolutionUnit::Dppx => self.value * 96.0,
99            ResolutionUnit::Dpcm => self.value * 2.54,
100        }
101    }
102
103    /// Returns the unit of the resolution.
104    #[inline]
105    pub fn resolution_unit(&self) -> ResolutionUnit {
106        self.unit
107    }
108
109    /// Parse a resolution given a value and unit.
110    pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
111        let unit = ResolutionUnit::from_str(unit)?;
112        Ok(Self::new(unit, value))
113    }
114}
115
116impl ToCss for NoCalcResolution {
117    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
118    where
119        W: Write,
120    {
121        crate::values::serialize_specified_dimension(
122            self.value,
123            self.unit.as_str(),
124            /* was_calc = */ false,
125            dest,
126        )
127    }
128}
129
130impl SpecifiedValueInfo for NoCalcResolution {}
131
132/// A specified resolution value, either a plain value or a `calc()` expression.
133///
134/// https://drafts.csswg.org/css-values/#resolution-value
135#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
136pub struct Resolution(NumericUnion<ResolutionUnit, f32, CalcNumeric>);
137
138impl ToCss for Resolution {
139    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
140    where
141        W: Write,
142    {
143        match self.0.unpack() {
144            Unpacked::Inline(unit, value) => NoCalcResolution::new(unit, value).to_css(dest),
145            Unpacked::Boxed(calc) => calc.to_css(dest),
146        }
147    }
148}
149
150impl SpecifiedValueInfo for Resolution {}
151
152impl Resolution {
153    /// Creates a resolution from a non-calc `NoCalcResolution`.
154    #[inline]
155    pub fn new(resolution: NoCalcResolution) -> Self {
156        Self(NumericUnion::inline(resolution.unit, resolution.value))
157    }
158
159    /// Creates a resolution from a `calc()` expression.
160    #[inline]
161    pub fn new_calc(calc: Box<CalcNumeric>) -> Self {
162        Self(NumericUnion::boxed(calc))
163    }
164
165    /// Returns a resolution value from dppx units.
166    #[inline]
167    pub fn from_dppx(value: CSSFloat) -> Self {
168        Self::new(NoCalcResolution::from_dppx(value))
169    }
170
171    /// Returns a resolution value from x units.
172    #[inline]
173    pub fn from_x(value: CSSFloat) -> Self {
174        Self::new(NoCalcResolution::from_x(value))
175    }
176
177    /// Returns true if this is a `calc()` expression.
178    #[inline]
179    pub fn is_calc(&self) -> bool {
180        self.0.is_boxed()
181    }
182
183    /// Parse a resolution given a value and unit.
184    pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Self, ()> {
185        NoCalcResolution::parse_dimension(value, unit).map(Self::new)
186    }
187}
188
189impl ToComputedValue for Resolution {
190    type ComputedValue = ComputedResolution;
191
192    #[inline]
193    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
194        let dppx = match self.0.unpack() {
195            Unpacked::Inline(unit, value) => NoCalcResolution::new(unit, value).dppx().max(0.0),
196            Unpacked::Boxed(calc) => calc.resolve(context, |result| match result {
197                Ok(Leaf::Resolution(r)) => r.dppx().max(0.0),
198                _ => {
199                    debug_assert!(
200                        false,
201                        "Unexpected Resolution::Calc without resolved resolution"
202                    );
203                    0.0
204                },
205            }),
206        };
207        ComputedResolution::from_dppx(crate::values::normalize(dppx))
208    }
209
210    #[inline]
211    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
212        Self::from_dppx(computed.dppx())
213    }
214}
215
216impl Parse for Resolution {
217    fn parse<'i, 't>(
218        context: &ParserContext,
219        input: &mut Parser<'i, 't>,
220    ) -> Result<Self, ParseError<'i>> {
221        let location = input.current_source_location();
222        match *input.next()? {
223            Token::Dimension {
224                value, ref unit, ..
225            } if value >= 0. => Self::parse_dimension(value, unit)
226                .map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
227            Token::Function(ref name) => {
228                let function = CalcNode::math_function(context, name, location)?;
229                CalcNode::parse_resolution(context, input, function)
230                    .map(Box::new)
231                    .map(Self::new_calc)
232            },
233            ref t => return Err(location.new_unexpected_token_error(t.clone())),
234        }
235    }
236}