Source code

Revision control

Copy as Markdown

Other Tools

/* 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.
//!
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::{
parse_important, AtRuleParser, CowRcStr, DeclarationParser, Parser, QualifiedRuleParser,
RuleBodyItemParser, RuleBodyParser,
};
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_char(';')
}
}
};
}
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:
#[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)),
}
}
type ViewportDeclarations = Vec<ViewportDescriptorDeclaration>;
impl<'a, 'b, 'i> AtRuleParser<'i> for ViewportRuleParser<'a, 'b> {
type Prelude = ();
type AtRule = ViewportDeclarations;
type Error = StyleParseErrorKind<'i>;
}
impl<'a, 'b, 'i> QualifiedRuleParser<'i> for ViewportRuleParser<'a, 'b> {
type Prelude = ();
type QualifiedRule = ViewportDeclarations;
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()))),
}
}
}
impl<'a, 'b, 'i> RuleBodyItemParser<'i, ViewportDeclarations, StyleParseErrorKind<'i>>
for ViewportRuleParser<'a, 'b>
{
fn parse_declarations(&self) -> bool {
true
}
fn parse_qualified(&self) -> bool {
false
}
}
/// 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 mut parser = ViewportRuleParser { context };
let mut cascade = Cascade::new();
let mut parser = RuleBodyParser::new(input, &mut 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
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 })
} 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_char(' ')?;
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.
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,
orientation,
})
}
}