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 //! Helper types for the `@viewport` rule.
6 
7 use crate::{CSSPixel, CssWriter, ParseError, PinchZoomFactor, ToCss};
8 use cssparser::Parser;
9 use euclid::Size2D;
10 use std::fmt::{self, Write};
11 
12 define_css_keyword_enum! {
13     pub enum UserZoom {
14         Zoom = "zoom",
15         Fixed = "fixed",
16     }
17 }
18 
19 define_css_keyword_enum! {
20     pub enum Orientation {
21         Auto = "auto",
22         Portrait = "portrait",
23         Landscape = "landscape",
24     }
25 }
26 
27 /// A set of viewport descriptors:
28 ///
29 /// <https://drafts.csswg.org/css-device-adapt/#viewport-desc>
30 #[derive(Clone, Debug, PartialEq)]
31 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize, MallocSizeOf))]
32 pub struct ViewportConstraints {
33     /// Width and height:
34     ///  * https://drafts.csswg.org/css-device-adapt/#width-desc
35     ///  * https://drafts.csswg.org/css-device-adapt/#height-desc
36     pub size: Size2D<f32, CSSPixel>,
37     /// <https://drafts.csswg.org/css-device-adapt/#zoom-desc>
38     pub initial_zoom: PinchZoomFactor,
39     /// <https://drafts.csswg.org/css-device-adapt/#min-max-width-desc>
40     pub min_zoom: Option<PinchZoomFactor>,
41     /// <https://drafts.csswg.org/css-device-adapt/#min-max-width-desc>
42     pub max_zoom: Option<PinchZoomFactor>,
43     /// <https://drafts.csswg.org/css-device-adapt/#user-zoom-desc>
44     pub user_zoom: UserZoom,
45     /// <https://drafts.csswg.org/css-device-adapt/#orientation-desc>
46     pub orientation: Orientation,
47 }
48 
49 impl ToCss for ViewportConstraints {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,50     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
51     where
52         W: Write,
53     {
54         dest.write_str("@viewport { width: ")?;
55         self.size.width.to_css(dest)?;
56 
57         dest.write_str("px; height: ")?;
58         self.size.height.to_css(dest)?;
59 
60         dest.write_str("px; zoom: ")?;
61         self.initial_zoom.get().to_css(dest)?;
62 
63         if let Some(min_zoom) = self.min_zoom {
64             dest.write_str("; min-zoom: ")?;
65             min_zoom.get().to_css(dest)?;
66         }
67 
68         if let Some(max_zoom) = self.max_zoom {
69             dest.write_str("; max-zoom: ")?;
70             max_zoom.get().to_css(dest)?;
71         }
72 
73         dest.write_str("; user-zoom: ")?;
74         self.user_zoom.to_css(dest)?;
75 
76         dest.write_str("; orientation: ")?;
77         self.orientation.to_css(dest)?;
78         dest.write_str("; }")
79     }
80 }
81 
82 /// <https://drafts.csswg.org/css-device-adapt/#descdef-viewport-zoom>
83 #[derive(Clone, Copy, Debug, PartialEq, ToShmem)]
84 #[cfg_attr(feature = "servo", derive(MallocSizeOf))]
85 pub enum Zoom {
86     /// A number value.
87     Number(f32),
88     /// A percentage value.
89     Percentage(f32),
90     /// The `auto` keyword.
91     Auto,
92 }
93 
94 impl ToCss for Zoom {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write,95     fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
96     where
97         W: fmt::Write,
98     {
99         match *self {
100             Zoom::Number(number) => number.to_css(dest),
101             Zoom::Auto => dest.write_str("auto"),
102             Zoom::Percentage(percentage) => {
103                 (percentage * 100.).to_css(dest)?;
104                 dest.write_char('%')
105             },
106         }
107     }
108 }
109 
110 impl Zoom {
111     /// Parse a zoom value per:
112     ///
113     /// <https://drafts.csswg.org/css-device-adapt/#descdef-viewport-zoom>
parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Zoom, ParseError<'i>>114     pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Zoom, ParseError<'i>> {
115         use crate::values::specified::AllowedNumericType::NonNegative;
116         use crate::ParsingMode;
117         use cssparser::Token;
118 
119         let location = input.current_source_location();
120         match *input.next()? {
121             // TODO: This parse() method should take ParserContext as an
122             // argument, and pass ParsingMode owned by the ParserContext to
123             // is_ok() instead of using ParsingMode::DEFAULT directly.
124             // In order to do so, we might want to move these stuff into style::stylesheets::viewport_rule.
125             Token::Percentage { unit_value, .. }
126                 if NonNegative.is_ok(ParsingMode::DEFAULT, unit_value) =>
127             {
128                 Ok(Zoom::Percentage(unit_value))
129             },
130             Token::Number { value, .. } if NonNegative.is_ok(ParsingMode::DEFAULT, value) => {
131                 Ok(Zoom::Number(value))
132             },
133             Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => Ok(Zoom::Auto),
134             ref t => Err(location.new_unexpected_token_error(t.clone())),
135         }
136     }
137 
138     /// Get this zoom value as a float value. Returns `None` if the value is the
139     /// `auto` keyword.
140     #[inline]
to_f32(&self) -> Option<f32>141     pub fn to_f32(&self) -> Option<f32> {
142         match *self {
143             Zoom::Number(number) => Some(number as f32),
144             Zoom::Percentage(percentage) => Some(percentage as f32),
145             Zoom::Auto => None,
146         }
147     }
148 }
149