diff options
Diffstat (limited to 'servo/components/style/stylesheets/viewport_rule.rs')
-rw-r--r-- | servo/components/style/stylesheets/viewport_rule.rs | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/servo/components/style/stylesheets/viewport_rule.rs b/servo/components/style/stylesheets/viewport_rule.rs new file mode 100644 index 0000000000..3d69f88e33 --- /dev/null +++ b/servo/components/style/stylesheets/viewport_rule.rs @@ -0,0 +1,791 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! The [`@viewport`][at] at-rule and [`meta`][meta] element. +//! +//! [at]: https://drafts.csswg.org/css-device-adapt/#atviewport-rule +//! [meta]: https://drafts.csswg.org/css-device-adapt/#viewport-meta + +use crate::context::QuirksMode; +use crate::error_reporting::ContextualParseError; +use crate::media_queries::Device; +use crate::parser::{Parse, ParserContext}; +use crate::properties::StyleBuilder; +use crate::rule_cache::RuleCacheConditions; +use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards, ToCssWithGuard}; +use crate::str::CssStringWriter; +use crate::stylesheets::cascading_at_rule::DescriptorDeclaration; +use crate::stylesheets::container_rule::ContainerSizeQuery; +use crate::stylesheets::{Origin, StylesheetInDocument}; +use crate::values::computed::{Context, ToComputedValue}; +use crate::values::generics::length::LengthPercentageOrAuto; +use crate::values::generics::NonNegative; +use crate::values::specified::{self, NoCalcLength}; +use crate::values::specified::{NonNegativeLengthPercentageOrAuto, ViewportPercentageLength}; +use app_units::Au; +use cssparser::CowRcStr; +use cssparser::{parse_important, AtRuleParser, DeclarationListParser, DeclarationParser, Parser}; +use euclid::Size2D; +use selectors::parser::SelectorParseErrorKind; +use std::borrow::Cow; +use std::fmt::{self, Write}; +use std::iter::Enumerate; +use std::str::Chars; +use style_traits::viewport::{Orientation, UserZoom, ViewportConstraints, Zoom}; +use style_traits::{CssWriter, ParseError, PinchZoomFactor, StyleParseErrorKind, ToCss}; + +/// Whether parsing and processing of `@viewport` rules is enabled. +#[cfg(feature = "servo")] +pub fn enabled() -> bool { + use servo_config::pref; + pref!(layout.viewport.enabled) +} + +/// Whether parsing and processing of `@viewport` rules is enabled. +#[cfg(not(feature = "servo"))] +pub fn enabled() -> bool { + false // Gecko doesn't support @viewport. +} + +macro_rules! declare_viewport_descriptor { + ( $( $variant_name: expr => $variant: ident($data: ident), )+ ) => { + declare_viewport_descriptor_inner!([] [ $( $variant_name => $variant($data), )+ ] 0); + }; +} + +macro_rules! declare_viewport_descriptor_inner { + ( + [ $( $assigned_variant_name: expr => + $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ] + [ + $next_variant_name: expr => $next_variant: ident($next_data: ident), + $( $variant_name: expr => $variant: ident($data: ident), )* + ] + $next_discriminant: expr + ) => { + declare_viewport_descriptor_inner! { + [ + $( $assigned_variant_name => $assigned_variant($assigned_data) = $assigned_discriminant, )* + $next_variant_name => $next_variant($next_data) = $next_discriminant, + ] + [ $( $variant_name => $variant($data), )* ] + $next_discriminant + 1 + } + }; + + ( + [ $( $assigned_variant_name: expr => + $assigned_variant: ident($assigned_data: ident) = $assigned_discriminant: expr, )* ] + [ ] + $number_of_variants: expr + ) => { + #[derive(Clone, Debug, PartialEq, ToShmem)] + #[cfg_attr(feature = "servo", derive(MallocSizeOf))] + #[allow(missing_docs)] + pub enum ViewportDescriptor { + $( + $assigned_variant($assigned_data), + )+ + } + + const VIEWPORT_DESCRIPTOR_VARIANTS: usize = $number_of_variants; + + impl ViewportDescriptor { + #[allow(missing_docs)] + pub fn discriminant_value(&self) -> usize { + match *self { + $( + ViewportDescriptor::$assigned_variant(..) => $assigned_discriminant, + )* + } + } + } + + impl ToCss for ViewportDescriptor { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + $( + ViewportDescriptor::$assigned_variant(ref val) => { + dest.write_str($assigned_variant_name)?; + dest.write_str(": ")?; + val.to_css(dest)?; + }, + )* + } + dest.write_str(";") + } + } + }; +} + +declare_viewport_descriptor! { + "min-width" => MinWidth(ViewportLength), + "max-width" => MaxWidth(ViewportLength), + + "min-height" => MinHeight(ViewportLength), + "max-height" => MaxHeight(ViewportLength), + + "zoom" => Zoom(Zoom), + "min-zoom" => MinZoom(Zoom), + "max-zoom" => MaxZoom(Zoom), + + "user-zoom" => UserZoom(UserZoom), + "orientation" => Orientation(Orientation), +} + +trait FromMeta: Sized { + fn from_meta(value: &str) -> Option<Self>; +} + +/// ViewportLength is a length | percentage | auto | extend-to-zoom +/// See: +/// * http://dev.w3.org/csswg/css-device-adapt/#min-max-width-desc +/// * http://dev.w3.org/csswg/css-device-adapt/#extend-to-zoom +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Clone, Debug, PartialEq, ToCss, ToShmem)] +pub enum ViewportLength { + Specified(NonNegativeLengthPercentageOrAuto), + ExtendToZoom, +} + +impl FromMeta for ViewportLength { + fn from_meta(value: &str) -> Option<ViewportLength> { + macro_rules! specified { + ($value:expr) => { + ViewportLength::Specified(LengthPercentageOrAuto::LengthPercentage(NonNegative( + specified::LengthPercentage::Length($value), + ))) + }; + } + + Some(match value { + v if v.eq_ignore_ascii_case("device-width") => specified!( + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vw(100.)) + ), + v if v.eq_ignore_ascii_case("device-height") => specified!( + NoCalcLength::ViewportPercentage(ViewportPercentageLength::Vh(100.)) + ), + _ => match value.parse::<f32>() { + Ok(n) if n >= 0. => specified!(NoCalcLength::from_px(n.max(1.).min(10000.))), + Ok(_) => return None, + Err(_) => specified!(NoCalcLength::from_px(1.)), + }, + }) + } +} + +impl ViewportLength { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + // we explicitly do not accept 'extend-to-zoom', since it is a UA + // internal value for <META> viewport translation + NonNegativeLengthPercentageOrAuto::parse(context, input).map(ViewportLength::Specified) + } +} + +impl FromMeta for Zoom { + fn from_meta(value: &str) -> Option<Zoom> { + Some(match value { + v if v.eq_ignore_ascii_case("yes") => Zoom::Number(1.), + v if v.eq_ignore_ascii_case("no") => Zoom::Number(0.1), + v if v.eq_ignore_ascii_case("device-width") => Zoom::Number(10.), + v if v.eq_ignore_ascii_case("device-height") => Zoom::Number(10.), + _ => match value.parse::<f32>() { + Ok(n) if n >= 0. => Zoom::Number(n.max(0.1).min(10.)), + Ok(_) => return None, + Err(_) => Zoom::Number(0.1), + }, + }) + } +} + +impl FromMeta for UserZoom { + fn from_meta(value: &str) -> Option<UserZoom> { + Some(match value { + v if v.eq_ignore_ascii_case("yes") => UserZoom::Zoom, + v if v.eq_ignore_ascii_case("no") => UserZoom::Fixed, + v if v.eq_ignore_ascii_case("device-width") => UserZoom::Zoom, + v if v.eq_ignore_ascii_case("device-height") => UserZoom::Zoom, + _ => match value.parse::<f32>() { + Ok(n) if n >= 1. || n <= -1. => UserZoom::Zoom, + _ => UserZoom::Fixed, + }, + }) + } +} + +struct ViewportRuleParser<'a, 'b: 'a> { + context: &'a ParserContext<'b>, +} + +#[allow(missing_docs)] +pub type ViewportDescriptorDeclaration = DescriptorDeclaration<ViewportDescriptor>; + +fn parse_shorthand<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, +) -> Result<(ViewportLength, ViewportLength), ParseError<'i>> { + let min = ViewportLength::parse(context, input)?; + match input.try_parse(|i| ViewportLength::parse(context, i)) { + Err(_) => Ok((min.clone(), min)), + Ok(max) => Ok((min, max)), + } +} + +impl<'a, 'b, 'i> AtRuleParser<'i> for ViewportRuleParser<'a, 'b> { + type Prelude = (); + type AtRule = Vec<ViewportDescriptorDeclaration>; + type Error = StyleParseErrorKind<'i>; +} + +impl<'a, 'b, 'i> DeclarationParser<'i> for ViewportRuleParser<'a, 'b> { + type Declaration = Vec<ViewportDescriptorDeclaration>; + type Error = StyleParseErrorKind<'i>; + + fn parse_value<'t>( + &mut self, + name: CowRcStr<'i>, + input: &mut Parser<'i, 't>, + ) -> Result<Vec<ViewportDescriptorDeclaration>, ParseError<'i>> { + macro_rules! declaration { + ($declaration:ident($parse:expr)) => { + declaration!($declaration { + value: $parse(input)?, + important: input.try_parse(parse_important).is_ok(), + }) + }; + ($declaration:ident { value: $value:expr, important: $important:expr, }) => { + ViewportDescriptorDeclaration::new( + self.context.stylesheet_origin, + ViewportDescriptor::$declaration($value), + $important, + ) + }; + } + + macro_rules! ok { + ($declaration:ident($parse:expr)) => { + Ok(vec![declaration!($declaration($parse))]) + }; + (shorthand -> [$min:ident, $max:ident]) => {{ + let shorthand = parse_shorthand(self.context, input)?; + let important = input.try_parse(parse_important).is_ok(); + + Ok(vec![ + declaration!($min { + value: shorthand.0, + important: important, + }), + declaration!($max { + value: shorthand.1, + important: important, + }), + ]) + }}; + } + + match_ignore_ascii_case! { &*name, + "min-width" => ok!(MinWidth(|i| ViewportLength::parse(self.context, i))), + "max-width" => ok!(MaxWidth(|i| ViewportLength::parse(self.context, i))), + "width" => ok!(shorthand -> [MinWidth, MaxWidth]), + "min-height" => ok!(MinHeight(|i| ViewportLength::parse(self.context, i))), + "max-height" => ok!(MaxHeight(|i| ViewportLength::parse(self.context, i))), + "height" => ok!(shorthand -> [MinHeight, MaxHeight]), + "zoom" => ok!(Zoom(Zoom::parse)), + "min-zoom" => ok!(MinZoom(Zoom::parse)), + "max-zoom" => ok!(MaxZoom(Zoom::parse)), + "user-zoom" => ok!(UserZoom(UserZoom::parse)), + "orientation" => ok!(Orientation(Orientation::parse)), + _ => Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))), + } + } +} + +/// A `@viewport` rule. +#[derive(Clone, Debug, PartialEq, ToShmem)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct ViewportRule { + /// The declarations contained in this @viewport rule. + pub declarations: Vec<ViewportDescriptorDeclaration>, +} + +/// Whitespace as defined by DEVICE-ADAPT § 9.2 +// TODO: should we just use whitespace as defined by HTML5? +const WHITESPACE: &'static [char] = &['\t', '\n', '\r', ' ']; + +/// Separators as defined by DEVICE-ADAPT § 9.2 +// need to use \x2c instead of ',' due to test-tidy +const SEPARATOR: &'static [char] = &['\x2c', ';']; + +#[inline] +fn is_whitespace_separator_or_equals(c: &char) -> bool { + WHITESPACE.contains(c) || SEPARATOR.contains(c) || *c == '=' +} + +impl ViewportRule { + /// Parse a single @viewport rule. + /// + /// TODO(emilio): This could use the `Parse` trait now. + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Self, ParseError<'i>> { + let parser = ViewportRuleParser { context }; + + let mut cascade = Cascade::new(); + let mut parser = DeclarationListParser::new(input, parser); + while let Some(result) = parser.next() { + match result { + Ok(declarations) => { + for declarations in declarations { + cascade.add(Cow::Owned(declarations)) + } + }, + Err((error, slice)) => { + let location = error.location; + let error = ContextualParseError::UnsupportedViewportDescriptorDeclaration( + slice, error, + ); + context.log_css_error(location, error); + }, + } + } + Ok(ViewportRule { + declarations: cascade.finish(), + }) + } +} + +impl ViewportRule { + #[allow(missing_docs)] + pub fn from_meta(content: &str) -> Option<ViewportRule> { + let mut declarations = vec![None; VIEWPORT_DESCRIPTOR_VARIANTS]; + macro_rules! push_descriptor { + ($descriptor:ident($value:expr)) => {{ + let descriptor = ViewportDescriptor::$descriptor($value); + let discriminant = descriptor.discriminant_value(); + declarations[discriminant] = Some(ViewportDescriptorDeclaration::new( + Origin::Author, + descriptor, + false, + )); + }}; + } + + let mut has_width = false; + let mut has_height = false; + let mut has_zoom = false; + + let mut iter = content.chars().enumerate(); + + macro_rules! start_of_name { + ($iter:ident) => { + $iter + .by_ref() + .skip_while(|&(_, c)| is_whitespace_separator_or_equals(&c)) + .next() + }; + } + + while let Some((start, _)) = start_of_name!(iter) { + let property = ViewportRule::parse_meta_property(content, &mut iter, start); + + if let Some((name, value)) = property { + macro_rules! push { + ($descriptor:ident($translate:path)) => { + if let Some(value) = $translate(value) { + push_descriptor!($descriptor(value)); + } + }; + } + + match name { + n if n.eq_ignore_ascii_case("width") => { + if let Some(value) = ViewportLength::from_meta(value) { + push_descriptor!(MinWidth(ViewportLength::ExtendToZoom)); + push_descriptor!(MaxWidth(value)); + has_width = true; + } + }, + n if n.eq_ignore_ascii_case("height") => { + if let Some(value) = ViewportLength::from_meta(value) { + push_descriptor!(MinHeight(ViewportLength::ExtendToZoom)); + push_descriptor!(MaxHeight(value)); + has_height = true; + } + }, + n if n.eq_ignore_ascii_case("initial-scale") => { + if let Some(value) = Zoom::from_meta(value) { + push_descriptor!(Zoom(value)); + has_zoom = true; + } + }, + n if n.eq_ignore_ascii_case("minimum-scale") => push!(MinZoom(Zoom::from_meta)), + n if n.eq_ignore_ascii_case("maximum-scale") => push!(MaxZoom(Zoom::from_meta)), + n if n.eq_ignore_ascii_case("user-scalable") => { + push!(UserZoom(UserZoom::from_meta)) + }, + _ => {}, + } + } + } + + // DEVICE-ADAPT § 9.4 - The 'width' and 'height' properties + // http://dev.w3.org/csswg/css-device-adapt/#width-and-height-properties + if !has_width && has_zoom { + if has_height { + push_descriptor!(MinWidth(ViewportLength::Specified( + LengthPercentageOrAuto::Auto + ))); + push_descriptor!(MaxWidth(ViewportLength::Specified( + LengthPercentageOrAuto::Auto + ))); + } else { + push_descriptor!(MinWidth(ViewportLength::ExtendToZoom)); + push_descriptor!(MaxWidth(ViewportLength::ExtendToZoom)); + } + } + + let declarations: Vec<_> = declarations.into_iter().filter_map(|entry| entry).collect(); + if !declarations.is_empty() { + Some(ViewportRule { + declarations: declarations, + }) + } else { + None + } + } + + fn parse_meta_property<'a>( + content: &'a str, + iter: &mut Enumerate<Chars<'a>>, + start: usize, + ) -> Option<(&'a str, &'a str)> { + fn end_of_token(iter: &mut Enumerate<Chars>) -> Option<(usize, char)> { + iter.by_ref() + .skip_while(|&(_, c)| !is_whitespace_separator_or_equals(&c)) + .next() + } + + fn skip_whitespace(iter: &mut Enumerate<Chars>) -> Option<(usize, char)> { + iter.by_ref() + .skip_while(|&(_, c)| WHITESPACE.contains(&c)) + .next() + } + + // <name> <whitespace>* '=' + let end = match end_of_token(iter) { + Some((end, c)) if WHITESPACE.contains(&c) => match skip_whitespace(iter) { + Some((_, c)) if c == '=' => end, + _ => return None, + }, + Some((end, c)) if c == '=' => end, + _ => return None, + }; + let name = &content[start..end]; + + // <whitespace>* <value> + let start = match skip_whitespace(iter) { + Some((start, c)) if !SEPARATOR.contains(&c) => start, + _ => return None, + }; + let value = match end_of_token(iter) { + Some((end, _)) => &content[start..end], + _ => &content[start..], + }; + + Some((name, value)) + } +} + +impl ToCssWithGuard for ViewportRule { + // Serialization of ViewportRule is not specced. + fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result { + dest.write_str("@viewport { ")?; + let mut iter = self.declarations.iter(); + iter.next().unwrap().to_css(&mut CssWriter::new(dest))?; + for declaration in iter { + dest.write_str(" ")?; + declaration.to_css(&mut CssWriter::new(dest))?; + } + dest.write_str(" }") + } +} + +#[allow(missing_docs)] +pub struct Cascade { + declarations: Vec<Option<(usize, ViewportDescriptorDeclaration)>>, + count_so_far: usize, +} + +#[allow(missing_docs)] +impl Cascade { + pub fn new() -> Self { + Cascade { + declarations: vec![None; VIEWPORT_DESCRIPTOR_VARIANTS], + count_so_far: 0, + } + } + + pub fn from_stylesheets<'a, I, S>( + stylesheets: I, + guards: &StylesheetGuards, + device: &Device, + ) -> Self + where + I: Iterator<Item = (&'a S, Origin)>, + S: StylesheetInDocument + 'static, + { + let mut cascade = Self::new(); + for (stylesheet, origin) in stylesheets { + stylesheet.effective_viewport_rules(device, guards.for_origin(origin), |rule| { + for declaration in &rule.declarations { + cascade.add(Cow::Borrowed(declaration)) + } + }) + } + cascade + } + + pub fn add(&mut self, declaration: Cow<ViewportDescriptorDeclaration>) { + let descriptor = declaration.descriptor.discriminant_value(); + + match self.declarations[descriptor] { + Some((ref mut order_of_appearance, ref mut entry_declaration)) => { + if declaration.higher_or_equal_precendence(entry_declaration) { + *entry_declaration = declaration.into_owned(); + *order_of_appearance = self.count_so_far; + } + }, + ref mut entry @ None => { + *entry = Some((self.count_so_far, declaration.into_owned())); + }, + } + self.count_so_far += 1; + } + + pub fn finish(mut self) -> Vec<ViewportDescriptorDeclaration> { + // sort the descriptors by order of appearance + self.declarations + .sort_by_key(|entry| entry.as_ref().map(|&(index, _)| index)); + self.declarations + .into_iter() + .filter_map(|entry| entry.map(|(_, decl)| decl)) + .collect() + } +} + +/// Just a helper trait to be able to implement methods on ViewportConstraints. +pub trait MaybeNew { + /// Create a ViewportConstraints from a viewport size and a `@viewport` + /// rule. + fn maybe_new( + device: &Device, + rule: &ViewportRule, + quirks_mode: QuirksMode, + ) -> Option<ViewportConstraints>; +} + +impl MaybeNew for ViewportConstraints { + fn maybe_new( + device: &Device, + rule: &ViewportRule, + quirks_mode: QuirksMode, + ) -> Option<ViewportConstraints> { + use std::cmp; + + if rule.declarations.is_empty() { + return None; + } + + let mut min_width = None; + let mut max_width = None; + + let mut min_height = None; + let mut max_height = None; + + let mut initial_zoom = None; + let mut min_zoom = None; + let mut max_zoom = None; + + let mut user_zoom = UserZoom::Zoom; + let mut orientation = Orientation::Auto; + + // collapse the list of declarations into descriptor values + for declaration in &rule.declarations { + match declaration.descriptor { + ViewportDescriptor::MinWidth(ref value) => min_width = Some(value), + ViewportDescriptor::MaxWidth(ref value) => max_width = Some(value), + + ViewportDescriptor::MinHeight(ref value) => min_height = Some(value), + ViewportDescriptor::MaxHeight(ref value) => max_height = Some(value), + + ViewportDescriptor::Zoom(value) => initial_zoom = value.to_f32(), + ViewportDescriptor::MinZoom(value) => min_zoom = value.to_f32(), + ViewportDescriptor::MaxZoom(value) => max_zoom = value.to_f32(), + + ViewportDescriptor::UserZoom(value) => user_zoom = value, + ViewportDescriptor::Orientation(value) => orientation = value, + } + } + + // TODO: return `None` if all descriptors are either absent or initial value + + macro_rules! choose { + ($op:ident, $opta:expr, $optb:expr) => { + match ($opta, $optb) { + (None, None) => None, + (a, None) => a, + (None, b) => b, + (Some(a), Some(b)) => Some(a.$op(b)), + } + }; + } + macro_rules! min { + ($opta:expr, $optb:expr) => { + choose!(min, $opta, $optb) + }; + } + macro_rules! max { + ($opta:expr, $optb:expr) => { + choose!(max, $opta, $optb) + }; + } + + // DEVICE-ADAPT § 6.2.1 Resolve min-zoom and max-zoom values + if min_zoom.is_some() && max_zoom.is_some() { + max_zoom = Some(min_zoom.unwrap().max(max_zoom.unwrap())) + } + + // DEVICE-ADAPT § 6.2.2 Constrain zoom value to the [min-zoom, max-zoom] range + if initial_zoom.is_some() { + initial_zoom = max!(min_zoom, min!(max_zoom, initial_zoom)); + } + + // DEVICE-ADAPT § 6.2.3 Resolve non-auto lengths to pixel lengths + let initial_viewport = device.au_viewport_size(); + + let mut conditions = RuleCacheConditions::default(); + let context = Context::new( + // Note: DEVICE-ADAPT § 5. states that relative length values are + // resolved against initial values + StyleBuilder::for_inheritance(device, None, None), + quirks_mode, + &mut conditions, + ContainerSizeQuery::none(), + ); + + // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom' + let extend_width; + let extend_height; + if let Some(extend_zoom) = max!(initial_zoom, max_zoom) { + let scale_factor = 1. / extend_zoom; + extend_width = Some(initial_viewport.width.scale_by(scale_factor)); + extend_height = Some(initial_viewport.height.scale_by(scale_factor)); + } else { + extend_width = None; + extend_height = None; + } + + macro_rules! to_pixel_length { + ($value:ident, $dimension:ident, $extend_to:ident => $auto_extend_to:expr) => { + if let Some($value) = $value { + match *$value { + ViewportLength::Specified(ref length) => match *length { + LengthPercentageOrAuto::Auto => None, + LengthPercentageOrAuto::LengthPercentage(ref lop) => Some( + lop.to_computed_value(&context) + .to_used_value(initial_viewport.$dimension), + ), + }, + ViewportLength::ExtendToZoom => { + // $extend_to will be 'None' if 'extend-to-zoom' is 'auto' + match ($extend_to, $auto_extend_to) { + (None, None) => None, + (a, None) => a, + (None, b) => b, + (a, b) => cmp::max(a, b), + } + }, + } + } else { + None + } + }; + } + + // DEVICE-ADAPT § 9.3 states that max-descriptors need to be resolved + // before min-descriptors. + // http://dev.w3.org/csswg/css-device-adapt/#resolve-extend-to-zoom + let max_width = to_pixel_length!(max_width, width, extend_width => None); + let max_height = to_pixel_length!(max_height, height, extend_height => None); + + let min_width = to_pixel_length!(min_width, width, extend_width => max_width); + let min_height = to_pixel_length!(min_height, height, extend_height => max_height); + + // DEVICE-ADAPT § 6.2.4 Resolve initial width and height from min/max descriptors + macro_rules! resolve { + ($min:ident, $max:ident, $initial:expr) => { + if $min.is_some() || $max.is_some() { + let max = match $max { + Some(max) => cmp::min(max, $initial), + None => $initial, + }; + + Some(match $min { + Some(min) => cmp::max(min, max), + None => max, + }) + } else { + None + } + }; + } + + let width = resolve!(min_width, max_width, initial_viewport.width); + let height = resolve!(min_height, max_height, initial_viewport.height); + + // DEVICE-ADAPT § 6.2.5 Resolve width value + let width = if width.is_none() && height.is_none() { + Some(initial_viewport.width) + } else { + width + }; + + let width = width.unwrap_or_else(|| match initial_viewport.height { + Au(0) => initial_viewport.width, + initial_height => { + let ratio = initial_viewport.width.to_f32_px() / initial_height.to_f32_px(); + Au::from_f32_px(height.unwrap().to_f32_px() * ratio) + }, + }); + + // DEVICE-ADAPT § 6.2.6 Resolve height value + let height = height.unwrap_or_else(|| match initial_viewport.width { + Au(0) => initial_viewport.height, + initial_width => { + let ratio = initial_viewport.height.to_f32_px() / initial_width.to_f32_px(); + Au::from_f32_px(width.to_f32_px() * ratio) + }, + }); + + Some(ViewportConstraints { + size: Size2D::new(width.to_f32_px(), height.to_f32_px()), + + // TODO: compute a zoom factor for 'auto' as suggested by DEVICE-ADAPT § 10. + initial_zoom: PinchZoomFactor::new(initial_zoom.unwrap_or(1.)), + min_zoom: min_zoom.map(PinchZoomFactor::new), + max_zoom: max_zoom.map(PinchZoomFactor::new), + + user_zoom: user_zoom, + orientation: orientation, + }) + } +} |