mirror of
https://github.com/servo/servo.git
synced 2025-07-16 03:43:38 +01:00
This implements the CSS `linear-gradient` property per the CSS-IMAGES specification: http://dev.w3.org/csswg/css-images-3/ Improves GitHub.
2264 lines
91 KiB
Mako
2264 lines
91 KiB
Mako
/* 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
// This file is a Mako template: http://www.makotemplates.org/
|
|
|
|
pub use std::ascii::StrAsciiExt;
|
|
|
|
use servo_util::logical_geometry::{WritingMode, LogicalMargin};
|
|
use sync::Arc;
|
|
pub use url::Url;
|
|
|
|
pub use cssparser::*;
|
|
pub use cssparser::ast::*;
|
|
pub use geom::SideOffsets2D;
|
|
pub use self::common_types::specified::{Angle, AngleAoc, AngleOrCorner, Bottom, CornerAoc};
|
|
pub use self::common_types::specified::{Left, Right, Top};
|
|
|
|
use errors::{ErrorLoggerIterator, log_css_error};
|
|
pub use parsing_utils::*;
|
|
pub use self::common_types::*;
|
|
use selector_matching::DeclarationBlock;
|
|
|
|
|
|
pub use self::property_bit_field::PropertyBitField;
|
|
pub mod common_types;
|
|
|
|
|
|
<%!
|
|
|
|
import re
|
|
|
|
def to_rust_ident(name):
|
|
name = name.replace("-", "_")
|
|
if name in ["static", "super", "box"]: # Rust keywords
|
|
name += "_"
|
|
return name
|
|
|
|
class Longhand(object):
|
|
def __init__(self, name, derived_from=None, experimental=False):
|
|
self.name = name
|
|
self.ident = to_rust_ident(name)
|
|
self.camel_case, _ = re.subn(
|
|
"_([a-z])",
|
|
lambda m: m.group(1).upper(),
|
|
self.ident.strip("_").capitalize())
|
|
self.style_struct = THIS_STYLE_STRUCT
|
|
self.experimental = experimental
|
|
if derived_from is None:
|
|
self.derived_from = None
|
|
else:
|
|
self.derived_from = [ to_rust_ident(name) for name in derived_from ]
|
|
|
|
class Shorthand(object):
|
|
def __init__(self, name, sub_properties):
|
|
self.name = name
|
|
self.ident = to_rust_ident(name)
|
|
self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties]
|
|
|
|
class StyleStruct(object):
|
|
def __init__(self, name, inherited):
|
|
self.name = name
|
|
self.ident = to_rust_ident(name.lower())
|
|
self.longhands = []
|
|
self.inherited = inherited
|
|
|
|
STYLE_STRUCTS = []
|
|
THIS_STYLE_STRUCT = None
|
|
LONGHANDS = []
|
|
LONGHANDS_BY_NAME = {}
|
|
DERIVED_LONGHANDS = {}
|
|
SHORTHANDS = []
|
|
|
|
def new_style_struct(name, is_inherited):
|
|
global THIS_STYLE_STRUCT
|
|
|
|
style_struct = StyleStruct(name, is_inherited)
|
|
STYLE_STRUCTS.append(style_struct)
|
|
THIS_STYLE_STRUCT = style_struct
|
|
return ""
|
|
|
|
def switch_to_style_struct(name):
|
|
global THIS_STYLE_STRUCT
|
|
|
|
for style_struct in STYLE_STRUCTS:
|
|
if style_struct.name == name:
|
|
THIS_STYLE_STRUCT = style_struct
|
|
return ""
|
|
fail()
|
|
%>
|
|
|
|
pub mod longhands {
|
|
pub use super::*;
|
|
pub use std;
|
|
|
|
pub fn computed_as_specified<T>(value: T, _context: &computed::Context) -> T {
|
|
value
|
|
}
|
|
|
|
<%def name="raw_longhand(name, no_super=False, derived_from=None, experimental=False)">
|
|
<%
|
|
if derived_from is not None:
|
|
derived_from = derived_from.split()
|
|
|
|
property = Longhand(name, derived_from=derived_from, experimental=experimental)
|
|
THIS_STYLE_STRUCT.longhands.append(property)
|
|
LONGHANDS.append(property)
|
|
LONGHANDS_BY_NAME[name] = property
|
|
|
|
if derived_from is not None:
|
|
for name in derived_from:
|
|
DERIVED_LONGHANDS.setdefault(name, []).append(property)
|
|
%>
|
|
pub mod ${property.ident} {
|
|
% if not no_super:
|
|
use super::*;
|
|
% endif
|
|
pub use self::computed_value::*;
|
|
${caller.body()}
|
|
% if derived_from is None:
|
|
pub fn parse_declared(input: &[ComponentValue], base_url: &Url)
|
|
-> Result<DeclaredValue<SpecifiedValue>, ()> {
|
|
match CSSWideKeyword::parse(input) {
|
|
Ok(InheritKeyword) => Ok(Inherit),
|
|
Ok(InitialKeyword) => Ok(Initial),
|
|
Ok(UnsetKeyword) => Ok(${
|
|
"Inherit" if THIS_STYLE_STRUCT.inherited else "Initial"}),
|
|
Err(()) => parse_specified(input, base_url),
|
|
}
|
|
}
|
|
% endif
|
|
}
|
|
</%def>
|
|
|
|
<%def name="longhand(name, no_super=False, derived_from=None, experimental=False)">
|
|
<%self:raw_longhand name="${name}" derived_from="${derived_from}"
|
|
experimental="${experimental}" no_super="${no_super}">
|
|
${caller.body()}
|
|
% if derived_from is None:
|
|
pub fn parse_specified(_input: &[ComponentValue], _base_url: &Url)
|
|
-> Result<DeclaredValue<SpecifiedValue>, ()> {
|
|
parse(_input, _base_url).map(super::SpecifiedValue)
|
|
}
|
|
% endif
|
|
</%self:raw_longhand>
|
|
</%def>
|
|
|
|
<%def name="single_component_value(name, derived_from=None, experimental=False)">
|
|
<%self:longhand name="${name}" derived_from="${derived_from}"
|
|
experimental="${experimental}">
|
|
${caller.body()}
|
|
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
|
|
one_component_value(input).and_then(|c| from_component_value(c, base_url))
|
|
}
|
|
</%self:longhand>
|
|
</%def>
|
|
|
|
<%def name="single_keyword_computed(name, values, experimental=False)">
|
|
<%self:single_component_value name="${name}" experimental="${experimental}">
|
|
${caller.body()}
|
|
pub mod computed_value {
|
|
#[allow(non_camel_case_types)]
|
|
#[deriving(PartialEq, Clone, FromPrimitive)]
|
|
pub enum T {
|
|
% for value in values.split():
|
|
${to_rust_ident(value)},
|
|
% endfor
|
|
}
|
|
}
|
|
pub type SpecifiedValue = computed_value::T;
|
|
#[inline] pub fn get_initial_value() -> computed_value::T {
|
|
${to_rust_ident(values.split()[0])}
|
|
}
|
|
pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
get_ident_lower(v).and_then(|keyword| {
|
|
match keyword.as_slice() {
|
|
% for value in values.split():
|
|
"${value}" => Ok(${to_rust_ident(value)}),
|
|
% endfor
|
|
_ => Err(()),
|
|
}
|
|
})
|
|
}
|
|
</%self:single_component_value>
|
|
</%def>
|
|
|
|
<%def name="single_keyword(name, values, experimental=False)">
|
|
<%self:single_keyword_computed name="${name}"
|
|
values="${values}"
|
|
experimental="${experimental}">
|
|
// The computed value is the same as the specified value.
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
</%self:single_keyword_computed>
|
|
</%def>
|
|
|
|
<%def name="predefined_type(name, type, initial_value, parse_method='parse')">
|
|
<%self:single_component_value name="${name}">
|
|
pub use super::super::common_types::computed::compute_${type} as to_computed_value;
|
|
pub type SpecifiedValue = specified::${type};
|
|
pub mod computed_value {
|
|
pub type T = super::super::computed::${type};
|
|
}
|
|
#[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} }
|
|
#[inline] pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
specified::${type}::${parse_method}(v)
|
|
}
|
|
</%self:single_component_value>
|
|
</%def>
|
|
|
|
|
|
// CSS 2.1, Section 8 - Box model
|
|
|
|
${new_style_struct("Margin", is_inherited=False)}
|
|
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
${predefined_type("margin-" + side, "LengthOrPercentageOrAuto",
|
|
"computed::LPA_Length(Au(0))")}
|
|
% endfor
|
|
|
|
${new_style_struct("Padding", is_inherited=False)}
|
|
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
${predefined_type("padding-" + side, "LengthOrPercentage",
|
|
"computed::LP_Length(Au(0))",
|
|
"parse_non_negative")}
|
|
% endfor
|
|
|
|
${new_style_struct("Border", is_inherited=False)}
|
|
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
${predefined_type("border-%s-color" % side, "CSSColor", "CurrentColor")}
|
|
% endfor
|
|
|
|
${single_keyword("border-top-style", values="none solid double dotted dashed hidden groove ridge inset outset")}
|
|
|
|
% for side in ["right", "bottom", "left"]:
|
|
<%self:longhand name="border-${side}-style">
|
|
pub use super::border_top_style::{get_initial_value, parse, to_computed_value};
|
|
pub type SpecifiedValue = super::border_top_style::SpecifiedValue;
|
|
pub mod computed_value {
|
|
pub type T = super::super::border_top_style::computed_value::T;
|
|
}
|
|
</%self:longhand>
|
|
% endfor
|
|
|
|
pub fn parse_border_width(component_value: &ComponentValue, _base_url: &Url)
|
|
-> Result<specified::Length, ()> {
|
|
match component_value {
|
|
&Ident(ref value) => {
|
|
match value.as_slice().to_ascii_lower().as_slice() {
|
|
"thin" => Ok(specified::Length::from_px(1.)),
|
|
"medium" => Ok(specified::Length::from_px(3.)),
|
|
"thick" => Ok(specified::Length::from_px(5.)),
|
|
_ => Err(())
|
|
}
|
|
},
|
|
_ => specified::Length::parse_non_negative(component_value)
|
|
}
|
|
}
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
<%self:longhand name="border-${side}-width">
|
|
pub type SpecifiedValue = specified::Length;
|
|
pub mod computed_value {
|
|
use super::super::Au;
|
|
pub type T = Au;
|
|
}
|
|
#[inline] pub fn get_initial_value() -> computed_value::T {
|
|
Au::from_px(3) // medium
|
|
}
|
|
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<SpecifiedValue, ()> {
|
|
one_component_value(input).and_then(|c| parse_border_width(c, base_url))
|
|
}
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
if !context.border_${side}_present {
|
|
Au(0)
|
|
} else {
|
|
computed::compute_Au(value, context)
|
|
}
|
|
}
|
|
</%self:longhand>
|
|
% endfor
|
|
|
|
${new_style_struct("PositionOffsets", is_inherited=False)}
|
|
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
${predefined_type(side, "LengthOrPercentageOrAuto",
|
|
"computed::LPA_Auto")}
|
|
% endfor
|
|
|
|
// CSS 2.1, Section 9 - Visual formatting model
|
|
|
|
${new_style_struct("Box", is_inherited=False)}
|
|
|
|
// TODO: don't parse values we don't support
|
|
<%self:single_keyword_computed name="display"
|
|
values="inline block inline-block
|
|
table inline-table table-row-group table-header-group table-footer-group
|
|
table-row table-column-group table-column table-cell table-caption
|
|
list-item
|
|
none">
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
// if context.is_root_element && value == list_item {
|
|
// return block
|
|
// }
|
|
if context.positioned || context.floated || context.is_root_element {
|
|
match value {
|
|
inline_table => table,
|
|
inline | inline_block
|
|
| table_row_group | table_column | table_column_group
|
|
| table_header_group | table_footer_group | table_row
|
|
| table_cell | table_caption
|
|
=> block,
|
|
_ => value,
|
|
}
|
|
} else {
|
|
value
|
|
}
|
|
}
|
|
</%self:single_keyword_computed>
|
|
|
|
${single_keyword("position", "static absolute relative fixed")}
|
|
${single_keyword("float", "none left right")}
|
|
${single_keyword("clear", "none left right both")}
|
|
|
|
<%self:longhand name="-servo-display-for-hypothetical-box" derived_from="display" no_super="True">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
pub use super::display::{SpecifiedValue, get_initial_value};
|
|
pub use super::display::{parse};
|
|
use super::computed;
|
|
use super::display;
|
|
|
|
pub mod computed_value {
|
|
pub type T = super::SpecifiedValue;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context)
|
|
-> computed_value::T {
|
|
context.display
|
|
}
|
|
|
|
</%self:longhand>
|
|
|
|
<%self:single_component_value name="z-index">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
pub type SpecifiedValue = computed_value::T;
|
|
pub mod computed_value {
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum T {
|
|
Auto,
|
|
Number(i32),
|
|
}
|
|
|
|
impl T {
|
|
pub fn number_or_zero(self) -> i32 {
|
|
match self {
|
|
Auto => 0,
|
|
Number(value) => value,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T {
|
|
Auto
|
|
}
|
|
fn from_component_value(input: &ComponentValue, _: &Url) -> Result<SpecifiedValue,()> {
|
|
match *input {
|
|
Ident(ref keyword) if keyword.as_slice().eq_ignore_ascii_case("auto") => Ok(Auto),
|
|
ast::Number(ast::NumericValue {
|
|
int_value: Some(value),
|
|
..
|
|
}) => Ok(Number(value as i32)),
|
|
_ => Err(())
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
${new_style_struct("InheritedBox", is_inherited=True)}
|
|
|
|
${single_keyword("direction", "ltr rtl", experimental=True)}
|
|
|
|
// CSS 2.1, Section 10 - Visual formatting model details
|
|
|
|
${switch_to_style_struct("Box")}
|
|
|
|
${predefined_type("width", "LengthOrPercentageOrAuto",
|
|
"computed::LPA_Auto",
|
|
"parse_non_negative")}
|
|
<%self:single_component_value name="height">
|
|
pub type SpecifiedValue = specified::LengthOrPercentageOrAuto;
|
|
pub mod computed_value {
|
|
pub type T = super::super::computed::LengthOrPercentageOrAuto;
|
|
}
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T { computed::LPA_Auto }
|
|
#[inline]
|
|
pub fn from_component_value(v: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
specified::LengthOrPercentageOrAuto::parse_non_negative(v)
|
|
}
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
match (value, context.inherited_height) {
|
|
(specified::LPA_Percentage(_), computed::LPA_Auto)
|
|
if !context.is_root_element && !context.positioned => {
|
|
computed::LPA_Auto
|
|
},
|
|
_ => computed::compute_LengthOrPercentageOrAuto(value, context)
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
${predefined_type("min-width", "LengthOrPercentage",
|
|
"computed::LP_Length(Au(0))",
|
|
"parse_non_negative")}
|
|
${predefined_type("max-width", "LengthOrPercentageOrNone",
|
|
"computed::LPN_None",
|
|
"parse_non_negative")}
|
|
|
|
${predefined_type("min-height", "LengthOrPercentage",
|
|
"computed::LP_Length(Au(0))",
|
|
"parse_non_negative")}
|
|
${predefined_type("max-height", "LengthOrPercentageOrNone",
|
|
"computed::LPN_None",
|
|
"parse_non_negative")}
|
|
|
|
${switch_to_style_struct("InheritedBox")}
|
|
|
|
<%self:single_component_value name="line-height">
|
|
#[deriving(Clone)]
|
|
pub enum SpecifiedValue {
|
|
SpecifiedNormal,
|
|
SpecifiedLength(specified::Length),
|
|
SpecifiedNumber(CSSFloat),
|
|
// percentage are the same as em.
|
|
}
|
|
/// normal | <number> | <length> | <percentage>
|
|
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
match input {
|
|
&ast::Number(ref value) if value.value >= 0.
|
|
=> Ok(SpecifiedNumber(value.value)),
|
|
&ast::Percentage(ref value) if value.value >= 0.
|
|
=> Ok(SpecifiedLength(specified::Em(value.value / 100.))),
|
|
&Dimension(ref value, ref unit) if value.value >= 0.
|
|
=> specified::Length::parse_dimension(value.value, unit.as_slice())
|
|
.map(SpecifiedLength),
|
|
&Ident(ref value) if value.as_slice().eq_ignore_ascii_case("normal")
|
|
=> Ok(SpecifiedNormal),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
pub mod computed_value {
|
|
use super::super::{Au, CSSFloat};
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum T {
|
|
Normal,
|
|
Length(Au),
|
|
Number(CSSFloat),
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T { Normal }
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
match value {
|
|
SpecifiedNormal => Normal,
|
|
SpecifiedLength(value) => Length(computed::compute_Au(value, context)),
|
|
SpecifiedNumber(value) => Number(value),
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
${switch_to_style_struct("Box")}
|
|
|
|
<%self:single_component_value name="vertical-align">
|
|
<% vertical_align_keywords = (
|
|
"baseline sub super top text-top middle bottom text-bottom".split()) %>
|
|
#[allow(non_camel_case_types)]
|
|
#[deriving(Clone)]
|
|
pub enum SpecifiedValue {
|
|
% for keyword in vertical_align_keywords:
|
|
Specified_${to_rust_ident(keyword)},
|
|
% endfor
|
|
SpecifiedLengthOrPercentage(specified::LengthOrPercentage),
|
|
}
|
|
/// baseline | sub | super | top | text-top | middle | bottom | text-bottom
|
|
/// | <percentage> | <length>
|
|
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
match input {
|
|
&Ident(ref value) => {
|
|
match value.as_slice().to_ascii_lower().as_slice() {
|
|
% for keyword in vertical_align_keywords:
|
|
"${keyword}" => Ok(Specified_${to_rust_ident(keyword)}),
|
|
% endfor
|
|
_ => Err(()),
|
|
}
|
|
},
|
|
_ => specified::LengthOrPercentage::parse_non_negative(input)
|
|
.map(SpecifiedLengthOrPercentage)
|
|
}
|
|
}
|
|
pub mod computed_value {
|
|
use super::super::{Au, CSSFloat};
|
|
#[allow(non_camel_case_types)]
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum T {
|
|
% for keyword in vertical_align_keywords:
|
|
${to_rust_ident(keyword)},
|
|
% endfor
|
|
Length(Au),
|
|
Percentage(CSSFloat),
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T { baseline }
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
match value {
|
|
% for keyword in vertical_align_keywords:
|
|
Specified_${to_rust_ident(keyword)} => ${to_rust_ident(keyword)},
|
|
% endfor
|
|
SpecifiedLengthOrPercentage(value)
|
|
=> match computed::compute_LengthOrPercentage(value, context) {
|
|
computed::LP_Length(value) => Length(value),
|
|
computed::LP_Percentage(value) => Percentage(value)
|
|
}
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
|
|
// CSS 2.1, Section 11 - Visual effects
|
|
// FIXME: Implement scrolling for `scroll` and `auto` (#2742).
|
|
${single_keyword("overflow", "visible hidden scroll auto")}
|
|
|
|
${switch_to_style_struct("InheritedBox")}
|
|
|
|
// TODO: collapse. Well, do tables first.
|
|
${single_keyword("visibility", "visible hidden")}
|
|
|
|
// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists
|
|
|
|
${switch_to_style_struct("Box")}
|
|
|
|
<%self:longhand name="content">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
pub mod computed_value {
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum ContentItem {
|
|
StringContent(String),
|
|
}
|
|
#[allow(non_camel_case_types)]
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum T {
|
|
normal,
|
|
none,
|
|
Content(Vec<ContentItem>),
|
|
}
|
|
}
|
|
pub type SpecifiedValue = computed_value::T;
|
|
#[inline] pub fn get_initial_value() -> computed_value::T { normal }
|
|
|
|
// normal | none | [ <string> ]+
|
|
// TODO: <uri>, <counter>, attr(<identifier>), open-quote, close-quote, no-open-quote, no-close-quote
|
|
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
|
|
match one_component_value(input) {
|
|
Ok(&Ident(ref keyword)) => {
|
|
match keyword.as_slice().to_ascii_lower().as_slice() {
|
|
"normal" => return Ok(normal),
|
|
"none" => return Ok(none),
|
|
_ => ()
|
|
}
|
|
},
|
|
_ => ()
|
|
}
|
|
let mut content = vec!();
|
|
for component_value in input.skip_whitespace() {
|
|
match component_value {
|
|
&QuotedString(ref value)
|
|
=> content.push(StringContent(value.clone())),
|
|
_ => return Err(()) // invalid/unsupported value
|
|
}
|
|
}
|
|
Ok(Content(content))
|
|
}
|
|
</%self:longhand>
|
|
// CSS 2.1, Section 13 - Paged media
|
|
|
|
// CSS 2.1, Section 14 - Colors and Backgrounds
|
|
|
|
${new_style_struct("Background", is_inherited=False)}
|
|
${predefined_type("background-color", "CSSColor",
|
|
"RGBAColor(RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")}
|
|
|
|
<%self:single_component_value name="background-image">
|
|
use super::common_types::specified as common_specified;
|
|
pub mod computed_value {
|
|
use super::super::super::common_types::computed;
|
|
#[deriving(Clone, PartialEq)]
|
|
pub type T = Option<computed::Image>;
|
|
}
|
|
#[deriving(Clone)]
|
|
pub type SpecifiedValue = Option<common_specified::Image>;
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T {
|
|
None
|
|
}
|
|
pub fn from_component_value(component_value: &ComponentValue, base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
match component_value {
|
|
&ast::Ident(ref value) if value.as_slice().eq_ignore_ascii_case("none") => {
|
|
Ok(None)
|
|
}
|
|
_ => {
|
|
match common_specified::Image::from_component_value(component_value,
|
|
base_url) {
|
|
Err(err) => Err(err),
|
|
Ok(result) => Ok(Some(result)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
match value {
|
|
None => None,
|
|
Some(image) => Some(image.to_computed_value(context)),
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
<%self:longhand name="background-position">
|
|
pub mod computed_value {
|
|
use super::super::super::common_types::computed::LengthOrPercentage;
|
|
|
|
#[deriving(PartialEq, Clone)]
|
|
pub struct T {
|
|
pub horizontal: LengthOrPercentage,
|
|
pub vertical: LengthOrPercentage,
|
|
}
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
pub struct SpecifiedValue {
|
|
pub horizontal: specified::LengthOrPercentage,
|
|
pub vertical: specified::LengthOrPercentage,
|
|
}
|
|
|
|
impl SpecifiedValue {
|
|
fn new(first: specified::PositionComponent, second: specified::PositionComponent)
|
|
-> Result<SpecifiedValue,()> {
|
|
let (horiz, vert) = match (category(first), category(second)) {
|
|
// Don't allow two vertical keywords or two horizontal keywords.
|
|
(HorizontalKeyword, HorizontalKeyword) |
|
|
(VerticalKeyword, VerticalKeyword) => return Err(()),
|
|
|
|
// Swap if both are keywords and vertical precedes horizontal.
|
|
(VerticalKeyword, HorizontalKeyword) |
|
|
(VerticalKeyword, OtherKeyword) |
|
|
(OtherKeyword, HorizontalKeyword) => (second, first),
|
|
|
|
// By default, horizontal is first.
|
|
_ => (first, second),
|
|
};
|
|
Ok(SpecifiedValue {
|
|
horizontal: horiz.to_length_or_percentage(),
|
|
vertical: vert.to_length_or_percentage(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Collapse `Position` into a few categories to simplify the above `match` expression.
|
|
enum PositionCategory {
|
|
HorizontalKeyword,
|
|
VerticalKeyword,
|
|
OtherKeyword,
|
|
LengthOrPercentage,
|
|
}
|
|
fn category(p: specified::PositionComponent) -> PositionCategory {
|
|
match p {
|
|
specified::Pos_Left | specified::Pos_Right => HorizontalKeyword,
|
|
specified::Pos_Top | specified::Pos_Bottom => VerticalKeyword,
|
|
specified::Pos_Center => OtherKeyword,
|
|
specified::Pos_Length(_) |
|
|
specified::Pos_Percentage(_) => LengthOrPercentage,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
computed_value::T {
|
|
horizontal: computed::compute_LengthOrPercentage(value.horizontal, context),
|
|
vertical: computed::compute_LengthOrPercentage(value.vertical, context),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T {
|
|
computed_value::T {
|
|
horizontal: computed::LP_Percentage(0.0),
|
|
vertical: computed::LP_Percentage(0.0),
|
|
}
|
|
}
|
|
|
|
pub fn parse_one(first: &ComponentValue) -> Result<SpecifiedValue, ()> {
|
|
let first = try!(specified::PositionComponent::parse(first));
|
|
// If only one value is provided, use `center` for the second.
|
|
SpecifiedValue::new(first, specified::Pos_Center)
|
|
}
|
|
|
|
pub fn parse_two(first: &ComponentValue, second: &ComponentValue)
|
|
-> Result<SpecifiedValue, ()> {
|
|
let first = try!(specified::PositionComponent::parse(first));
|
|
let second = try!(specified::PositionComponent::parse(second));
|
|
SpecifiedValue::new(first, second)
|
|
}
|
|
|
|
pub fn parse(input: &[ComponentValue], _: &Url) -> Result<SpecifiedValue, ()> {
|
|
let mut input_iter = input.skip_whitespace();
|
|
let first = input_iter.next();
|
|
let second = input_iter.next();
|
|
if input_iter.next().is_some() {
|
|
return Err(())
|
|
}
|
|
match (first, second) {
|
|
(Some(first), Some(second)) => {
|
|
parse_two(first, second)
|
|
}
|
|
(Some(first), None) => {
|
|
parse_one(first)
|
|
}
|
|
_ => Err(())
|
|
}
|
|
}
|
|
</%self:longhand>
|
|
|
|
${single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")}
|
|
|
|
${single_keyword("background-attachment", "scroll fixed")}
|
|
|
|
${new_style_struct("Color", is_inherited=True)}
|
|
|
|
<%self:raw_longhand name="color">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
pub type SpecifiedValue = RGBA;
|
|
pub mod computed_value {
|
|
pub type T = super::SpecifiedValue;
|
|
}
|
|
#[inline] pub fn get_initial_value() -> computed_value::T {
|
|
RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */
|
|
}
|
|
pub fn parse_specified(input: &[ComponentValue], _base_url: &Url)
|
|
-> Result<DeclaredValue<SpecifiedValue>, ()> {
|
|
match one_component_value(input).and_then(Color::parse) {
|
|
Ok(RGBAColor(rgba)) => Ok(SpecifiedValue(rgba)),
|
|
Ok(CurrentColor) => Ok(Inherit),
|
|
Err(()) => Err(()),
|
|
}
|
|
}
|
|
</%self:raw_longhand>
|
|
|
|
// CSS 2.1, Section 15 - Fonts
|
|
|
|
${new_style_struct("Font", is_inherited=True)}
|
|
|
|
<%self:longhand name="font-family">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
pub mod computed_value {
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum FontFamily {
|
|
FamilyName(String),
|
|
// Generic
|
|
// Serif,
|
|
// SansSerif,
|
|
// Cursive,
|
|
// Fantasy,
|
|
// Monospace,
|
|
}
|
|
impl FontFamily {
|
|
pub fn name(&self) -> &str {
|
|
match *self {
|
|
FamilyName(ref name) => name.as_slice(),
|
|
}
|
|
}
|
|
}
|
|
pub type T = Vec<FontFamily>;
|
|
}
|
|
pub type SpecifiedValue = computed_value::T;
|
|
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T {
|
|
vec![FamilyName("serif".to_string())]
|
|
}
|
|
/// <familiy-name>#
|
|
/// <familiy-name> = <string> | [ <ident>+ ]
|
|
/// TODO: <generic-familiy>
|
|
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
|
|
parse_slice_comma_separated(input, parse_one_family)
|
|
}
|
|
pub fn parse_one_family<'a>(iter: ParserIter) -> Result<FontFamily, ()> {
|
|
// TODO: avoid copying strings?
|
|
let mut idents = match iter.next() {
|
|
Some(&QuotedString(ref value)) => return Ok(FamilyName(value.clone())),
|
|
Some(&Ident(ref value)) => {
|
|
// match value.as_slice().to_ascii_lower().as_slice() {
|
|
// "serif" => return Ok(Serif),
|
|
// "sans-serif" => return Ok(SansSerif),
|
|
// "cursive" => return Ok(Cursive),
|
|
// "fantasy" => return Ok(Fantasy),
|
|
// "monospace" => return Ok(Monospace),
|
|
// _ => {
|
|
vec![value.as_slice()]
|
|
// }
|
|
// }
|
|
}
|
|
_ => return Err(())
|
|
};
|
|
loop {
|
|
match iter.next() {
|
|
Some(&Ident(ref value)) => {
|
|
idents.push(value.as_slice());
|
|
iter.next();
|
|
}
|
|
Some(component_value) => {
|
|
iter.push_back(component_value);
|
|
break
|
|
}
|
|
None => break,
|
|
}
|
|
}
|
|
Ok(FamilyName(idents.connect(" ")))
|
|
}
|
|
</%self:longhand>
|
|
|
|
|
|
${single_keyword("font-style", "normal italic oblique")}
|
|
${single_keyword("font-variant", "normal small-caps")}
|
|
|
|
<%self:single_component_value name="font-weight">
|
|
#[deriving(Clone)]
|
|
pub enum SpecifiedValue {
|
|
Bolder,
|
|
Lighter,
|
|
% for weight in range(100, 901, 100):
|
|
SpecifiedWeight${weight},
|
|
% endfor
|
|
}
|
|
/// normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900
|
|
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
match input {
|
|
&Ident(ref value) => {
|
|
match value.as_slice().to_ascii_lower().as_slice() {
|
|
"bold" => Ok(SpecifiedWeight700),
|
|
"normal" => Ok(SpecifiedWeight400),
|
|
"bolder" => Ok(Bolder),
|
|
"lighter" => Ok(Lighter),
|
|
_ => Err(()),
|
|
}
|
|
},
|
|
&Number(ref value) => match value.int_value {
|
|
Some(100) => Ok(SpecifiedWeight100),
|
|
Some(200) => Ok(SpecifiedWeight200),
|
|
Some(300) => Ok(SpecifiedWeight300),
|
|
Some(400) => Ok(SpecifiedWeight400),
|
|
Some(500) => Ok(SpecifiedWeight500),
|
|
Some(600) => Ok(SpecifiedWeight600),
|
|
Some(700) => Ok(SpecifiedWeight700),
|
|
Some(800) => Ok(SpecifiedWeight800),
|
|
Some(900) => Ok(SpecifiedWeight900),
|
|
_ => Err(()),
|
|
},
|
|
_ => Err(())
|
|
}
|
|
}
|
|
pub mod computed_value {
|
|
#[deriving(PartialEq, Clone)]
|
|
pub enum T {
|
|
% for weight in range(100, 901, 100):
|
|
Weight${weight},
|
|
% endfor
|
|
}
|
|
impl T {
|
|
pub fn is_bold(self) -> bool {
|
|
match self {
|
|
Weight900 | Weight800 | Weight700 | Weight600 => true,
|
|
_ => false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T { Weight400 } // normal
|
|
#[inline]
|
|
pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
match value {
|
|
% for weight in range(100, 901, 100):
|
|
SpecifiedWeight${weight} => Weight${weight},
|
|
% endfor
|
|
Bolder => match context.inherited_font_weight {
|
|
Weight100 => Weight400,
|
|
Weight200 => Weight400,
|
|
Weight300 => Weight400,
|
|
Weight400 => Weight700,
|
|
Weight500 => Weight700,
|
|
Weight600 => Weight900,
|
|
Weight700 => Weight900,
|
|
Weight800 => Weight900,
|
|
Weight900 => Weight900,
|
|
},
|
|
Lighter => match context.inherited_font_weight {
|
|
Weight100 => Weight100,
|
|
Weight200 => Weight100,
|
|
Weight300 => Weight100,
|
|
Weight400 => Weight100,
|
|
Weight500 => Weight100,
|
|
Weight600 => Weight400,
|
|
Weight700 => Weight400,
|
|
Weight800 => Weight700,
|
|
Weight900 => Weight700,
|
|
},
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
<%self:single_component_value name="font-size">
|
|
pub type SpecifiedValue = specified::Length; // Percentages are the same as em.
|
|
pub mod computed_value {
|
|
use super::super::Au;
|
|
pub type T = Au;
|
|
}
|
|
static MEDIUM_PX: int = 16;
|
|
#[inline] pub fn get_initial_value() -> computed_value::T {
|
|
Au::from_px(MEDIUM_PX)
|
|
}
|
|
#[inline]
|
|
pub fn to_computed_value(_value: SpecifiedValue, context: &computed::Context)
|
|
-> computed_value::T {
|
|
// We already computed this element's font size; no need to compute it again.
|
|
return context.font_size
|
|
}
|
|
/// <length> | <percentage> | <absolute-size> | <relative-size>
|
|
pub fn from_component_value(input: &ComponentValue, _base_url: &Url)
|
|
-> Result<SpecifiedValue, ()> {
|
|
match specified::LengthOrPercentage::parse_non_negative(input) {
|
|
Ok(specified::LP_Length(value)) => return Ok(value),
|
|
Ok(specified::LP_Percentage(value)) => return Ok(specified::Em(value)),
|
|
Err(()) => (),
|
|
}
|
|
match try!(get_ident_lower(input)).as_slice() {
|
|
"xx-small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 5)),
|
|
"x-small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 4)),
|
|
"small" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 8 / 9)),
|
|
"medium" => Ok(specified::Au_(Au::from_px(MEDIUM_PX))),
|
|
"large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 6 / 5)),
|
|
"x-large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 3 / 2)),
|
|
"xx-large" => Ok(specified::Au_(Au::from_px(MEDIUM_PX) * 2)),
|
|
|
|
// https://github.com/servo/servo/issues/3423#issuecomment-56321664
|
|
"smaller" => Ok(specified::Em(0.85)),
|
|
"larger" => Ok(specified::Em(1.2)),
|
|
|
|
_ => return Err(())
|
|
}
|
|
}
|
|
</%self:single_component_value>
|
|
|
|
// CSS 2.1, Section 16 - Text
|
|
|
|
${new_style_struct("InheritedText", is_inherited=True)}
|
|
|
|
// TODO: initial value should be 'start' (CSS Text Level 3, direction-dependent.)
|
|
${single_keyword("text-align", "left right center justify")}
|
|
|
|
${new_style_struct("Text", is_inherited=False)}
|
|
|
|
<%self:longhand name="text-decoration">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
#[deriving(PartialEq, Clone)]
|
|
pub struct SpecifiedValue {
|
|
pub underline: bool,
|
|
pub overline: bool,
|
|
pub line_through: bool,
|
|
// 'blink' is accepted in the parser but ignored.
|
|
// Just not blinking the text is a conforming implementation per CSS 2.1.
|
|
}
|
|
pub mod computed_value {
|
|
pub type T = super::SpecifiedValue;
|
|
pub static none: T = super::SpecifiedValue { underline: false, overline: false, line_through: false };
|
|
}
|
|
#[inline] pub fn get_initial_value() -> computed_value::T {
|
|
none
|
|
}
|
|
/// none | [ underline || overline || line-through || blink ]
|
|
pub fn parse(input: &[ComponentValue], _base_url: &Url) -> Result<SpecifiedValue, ()> {
|
|
let mut result = SpecifiedValue {
|
|
underline: false, overline: false, line_through: false,
|
|
};
|
|
match one_component_value(input) {
|
|
Ok(&Ident(ref value))
|
|
if value.as_slice().eq_ignore_ascii_case("none") => return Ok(result),
|
|
_ => {}
|
|
}
|
|
let mut blink = false;
|
|
let mut empty = true;
|
|
for component_value in input.skip_whitespace() {
|
|
match get_ident_lower(component_value) {
|
|
Err(()) => return Err(()),
|
|
Ok(keyword) => match keyword.as_slice() {
|
|
"underline" => if result.underline { return Err(()) }
|
|
else { empty = false; result.underline = true },
|
|
"overline" => if result.overline { return Err(()) }
|
|
else { empty = false; result.overline = true },
|
|
"line-through" => if result.line_through { return Err(()) }
|
|
else { empty = false; result.line_through = true },
|
|
"blink" => if blink { return Err(()) }
|
|
else { empty = false; blink = true },
|
|
_ => return Err(()),
|
|
}
|
|
}
|
|
}
|
|
if !empty { Ok(result) } else { Err(()) }
|
|
}
|
|
</%self:longhand>
|
|
|
|
${switch_to_style_struct("InheritedText")}
|
|
|
|
<%self:longhand name="-servo-text-decorations-in-effect"
|
|
derived_from="display text-decoration">
|
|
pub use super::computed_as_specified as to_computed_value;
|
|
|
|
#[deriving(Clone, PartialEq)]
|
|
pub struct SpecifiedValue {
|
|
pub underline: Option<RGBA>,
|
|
pub overline: Option<RGBA>,
|
|
pub line_through: Option<RGBA>,
|
|
}
|
|
|
|
pub mod computed_value {
|
|
pub type T = super::SpecifiedValue;
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_initial_value() -> computed_value::T {
|
|
SpecifiedValue {
|
|
underline: None,
|
|
overline: None,
|
|
line_through: None,
|
|
}
|
|
}
|
|
|
|
fn maybe(flag: bool, context: &computed::Context) -> Option<RGBA> {
|
|
if flag {
|
|
Some(context.color)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn derive(context: &computed::Context) -> computed_value::T {
|
|
// Start with no declarations if this is a block; otherwise, start with the
|
|
// declarations in effect and add in the text decorations that this inline specifies.
|
|
let mut result = match context.display {
|
|
display::computed_value::inline => context.inherited_text_decorations_in_effect,
|
|
_ => {
|
|
SpecifiedValue {
|
|
underline: None,
|
|
overline: None,
|
|
line_through: None,
|
|
}
|
|
}
|
|
};
|
|
|
|
if result.underline.is_none() {
|
|
result.underline = maybe(context.text_decoration.underline, context)
|
|
}
|
|
if result.overline.is_none() {
|
|
result.overline = maybe(context.text_decoration.overline, context)
|
|
}
|
|
if result.line_through.is_none() {
|
|
result.line_through = maybe(context.text_decoration.line_through, context)
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
#[inline]
|
|
pub fn derive_from_text_decoration(_: text_decoration::computed_value::T,
|
|
context: &computed::Context)
|
|
-> computed_value::T {
|
|
derive(context)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn derive_from_display(_: display::computed_value::T, context: &computed::Context)
|
|
-> computed_value::T {
|
|
derive(context)
|
|
}
|
|
</%self:longhand>
|
|
|
|
${single_keyword("white-space", "normal pre nowrap")}
|
|
|
|
// CSS 2.1, Section 17 - Tables
|
|
${new_style_struct("Table", is_inherited=False)}
|
|
|
|
${single_keyword("table-layout", "auto fixed")}
|
|
|
|
// CSS 2.1, Section 18 - User interface
|
|
|
|
|
|
// CSS Writing Modes Level 3
|
|
// http://dev.w3.org/csswg/css-writing-modes/
|
|
${switch_to_style_struct("InheritedBox")}
|
|
|
|
${single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True)}
|
|
|
|
// FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support)
|
|
// FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented
|
|
${single_keyword("text-orientation", "sideways sideways-left sideways-right", experimental=True)}
|
|
|
|
// CSS Basic User Interface Module Level 3
|
|
// http://dev.w3.org/csswg/css-ui/
|
|
${switch_to_style_struct("Box")}
|
|
|
|
${single_keyword("box-sizing", "content-box border-box")}
|
|
}
|
|
|
|
|
|
pub mod shorthands {
|
|
pub use super::*;
|
|
pub use super::longhands::*;
|
|
|
|
<%def name="shorthand(name, sub_properties)">
|
|
<%
|
|
shorthand = Shorthand(name, sub_properties.split())
|
|
SHORTHANDS.append(shorthand)
|
|
%>
|
|
pub mod ${shorthand.ident} {
|
|
use super::*;
|
|
pub struct Longhands {
|
|
% for sub_property in shorthand.sub_properties:
|
|
pub ${sub_property.ident}: Option<${sub_property.ident}::SpecifiedValue>,
|
|
% endfor
|
|
}
|
|
pub fn parse(input: &[ComponentValue], base_url: &Url) -> Result<Longhands, ()> {
|
|
${caller.body()}
|
|
}
|
|
}
|
|
</%def>
|
|
|
|
<%def name="four_sides_shorthand(name, sub_property_pattern, parser_function)">
|
|
<%self:shorthand name="${name}" sub_properties="${
|
|
' '.join(sub_property_pattern % side
|
|
for side in ['top', 'right', 'bottom', 'left'])}">
|
|
let mut iter = input.skip_whitespace().map(|c| ${parser_function}(c, base_url).ok());
|
|
// zero or more than four values is invalid.
|
|
// one value sets them all
|
|
// two values set (top, bottom) and (left, right)
|
|
// three values set top, (left, right) and bottom
|
|
// four values set them in order
|
|
let top = iter.next().unwrap_or(None);
|
|
let right = iter.next().unwrap_or(top);
|
|
let bottom = iter.next().unwrap_or(top);
|
|
let left = iter.next().unwrap_or(right);
|
|
if top.is_some() && right.is_some() && bottom.is_some() && left.is_some()
|
|
&& iter.next().is_none() {
|
|
Ok(Longhands {
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
${to_rust_ident(sub_property_pattern % side)}: ${side},
|
|
% endfor
|
|
})
|
|
} else {
|
|
Err(())
|
|
}
|
|
</%self:shorthand>
|
|
</%def>
|
|
|
|
// TODO: other background-* properties
|
|
<%self:shorthand name="background"
|
|
sub_properties="background-color background-position background-repeat background-attachment background-image">
|
|
use std::mem;
|
|
|
|
let (mut color, mut image, mut position, mut repeat, mut attachment) =
|
|
(None, None, None, None, None);
|
|
let mut unused_component_value = None;
|
|
let mut any = false;
|
|
|
|
for component_value in input.skip_whitespace() {
|
|
// Try `background-position` first because it might not use the value.
|
|
if position.is_none() {
|
|
match mem::replace(&mut unused_component_value, None) {
|
|
Some(saved_component_value) => {
|
|
// First try parsing a pair of values, then a single value.
|
|
match background_position::parse_two(saved_component_value,
|
|
component_value) {
|
|
Ok(v) => {
|
|
position = Some(v);
|
|
any = true;
|
|
continue
|
|
},
|
|
Err(()) => {
|
|
match background_position::parse_one(saved_component_value) {
|
|
Ok(v) => {
|
|
position = Some(v);
|
|
any = true;
|
|
// We haven't used the current `component_value`;
|
|
// keep attempting to parse it below.
|
|
},
|
|
// If we get here, parsing failed.
|
|
Err(()) => return Err(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => () // Wait until we have a pair of potential values.
|
|
}
|
|
}
|
|
|
|
if color.is_none() {
|
|
match background_color::from_component_value(component_value, base_url) {
|
|
Ok(v) => {
|
|
color = Some(v);
|
|
any = true;
|
|
continue
|
|
},
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
|
|
if image.is_none() {
|
|
match background_image::from_component_value(component_value, base_url) {
|
|
Ok(v) => {
|
|
image = Some(v);
|
|
any = true;
|
|
continue
|
|
},
|
|
Err(()) => (),
|
|
}
|
|
}
|
|
|
|
if repeat.is_none() {
|
|
match background_repeat::from_component_value(component_value, base_url) {
|
|
Ok(v) => {
|
|
repeat = Some(v);
|
|
any = true;
|
|
continue
|
|
},
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
|
|
if attachment.is_none() {
|
|
match background_attachment::from_component_value(component_value,
|
|
base_url) {
|
|
Ok(v) => {
|
|
attachment = Some(v);
|
|
any = true;
|
|
continue
|
|
},
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
|
|
// Save the component value. It may the first of a background-position pair.
|
|
unused_component_value = Some(component_value);
|
|
}
|
|
|
|
if position.is_none() {
|
|
// Check for a lone trailing background-position value.
|
|
match mem::replace(&mut unused_component_value, None) {
|
|
Some(saved_component_value) => {
|
|
match background_position::parse_one(saved_component_value) {
|
|
Ok(v) => {
|
|
position = Some(v);
|
|
any = true;
|
|
},
|
|
Err(()) => return Err(())
|
|
}
|
|
}
|
|
None => ()
|
|
}
|
|
}
|
|
|
|
if any && unused_component_value.is_none() {
|
|
Ok(Longhands {
|
|
background_color: color,
|
|
background_image: image,
|
|
background_position: position,
|
|
background_repeat: repeat,
|
|
background_attachment: attachment,
|
|
})
|
|
} else {
|
|
Err(())
|
|
}
|
|
</%self:shorthand>
|
|
|
|
${four_sides_shorthand("margin", "margin-%s", "margin_top::from_component_value")}
|
|
${four_sides_shorthand("padding", "padding-%s", "padding_top::from_component_value")}
|
|
|
|
pub fn parse_color(value: &ComponentValue, _base_url: &Url) -> Result<specified::CSSColor, ()> {
|
|
specified::CSSColor::parse(value)
|
|
}
|
|
${four_sides_shorthand("border-color", "border-%s-color", "parse_color")}
|
|
${four_sides_shorthand("border-style", "border-%s-style",
|
|
"border_top_style::from_component_value")}
|
|
${four_sides_shorthand("border-width", "border-%s-width", "parse_border_width")}
|
|
|
|
pub fn parse_border(input: &[ComponentValue], base_url: &Url)
|
|
-> Result<(Option<specified::CSSColor>,
|
|
Option<border_top_style::SpecifiedValue>,
|
|
Option<specified::Length>), ()> {
|
|
let mut color = None;
|
|
let mut style = None;
|
|
let mut width = None;
|
|
let mut any = false;
|
|
for component_value in input.skip_whitespace() {
|
|
if color.is_none() {
|
|
match specified::CSSColor::parse(component_value) {
|
|
Ok(c) => { color = Some(c); any = true; continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
if style.is_none() {
|
|
match border_top_style::from_component_value(component_value, base_url) {
|
|
Ok(s) => { style = Some(s); any = true; continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
if width.is_none() {
|
|
match parse_border_width(component_value, base_url) {
|
|
Ok(w) => { width = Some(w); any = true; continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
return Err(())
|
|
}
|
|
if any { Ok((color, style, width)) } else { Err(()) }
|
|
}
|
|
|
|
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
<%self:shorthand name="border-${side}" sub_properties="${' '.join(
|
|
'border-%s-%s' % (side, prop)
|
|
for prop in ['color', 'style', 'width']
|
|
)}">
|
|
parse_border(input, base_url).map(|(color, style, width)| {
|
|
Longhands {
|
|
% for prop in ["color", "style", "width"]:
|
|
${"border_%s_%s: %s," % (side, prop, prop)}
|
|
% endfor
|
|
}
|
|
})
|
|
</%self:shorthand>
|
|
% endfor
|
|
|
|
<%self:shorthand name="border" sub_properties="${' '.join(
|
|
'border-%s-%s' % (side, prop)
|
|
for side in ['top', 'right', 'bottom', 'left']
|
|
for prop in ['color', 'style', 'width']
|
|
)}">
|
|
parse_border(input, base_url).map(|(color, style, width)| {
|
|
Longhands {
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
% for prop in ["color", "style", "width"]:
|
|
${"border_%s_%s: %s," % (side, prop, prop)}
|
|
% endfor
|
|
% endfor
|
|
}
|
|
})
|
|
</%self:shorthand>
|
|
|
|
<%self:shorthand name="font" sub_properties="font-style font-variant font-weight
|
|
font-size line-height font-family">
|
|
let mut iter = input.skip_whitespace();
|
|
let mut nb_normals = 0u;
|
|
let mut style = None;
|
|
let mut variant = None;
|
|
let mut weight = None;
|
|
let mut size = None;
|
|
let mut line_height = None;
|
|
for component_value in iter {
|
|
// Special-case 'normal' because it is valid in each of
|
|
// font-style, font-weight and font-variant.
|
|
// Leaves the values to None, 'normal' is the initial value for each of them.
|
|
match get_ident_lower(component_value) {
|
|
Ok(ref ident) if ident.as_slice().eq_ignore_ascii_case("normal") => {
|
|
nb_normals += 1;
|
|
continue;
|
|
}
|
|
_ => {}
|
|
}
|
|
if style.is_none() {
|
|
match font_style::from_component_value(component_value, base_url) {
|
|
Ok(s) => { style = Some(s); continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
if weight.is_none() {
|
|
match font_weight::from_component_value(component_value, base_url) {
|
|
Ok(w) => { weight = Some(w); continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
if variant.is_none() {
|
|
match font_variant::from_component_value(component_value, base_url) {
|
|
Ok(v) => { variant = Some(v); continue },
|
|
Err(()) => ()
|
|
}
|
|
}
|
|
match font_size::from_component_value(component_value, base_url) {
|
|
Ok(s) => { size = Some(s); break },
|
|
Err(()) => return Err(())
|
|
}
|
|
}
|
|
#[inline]
|
|
fn count<T>(opt: &Option<T>) -> uint {
|
|
match opt {
|
|
&Some(_) => 1,
|
|
&None => 0,
|
|
}
|
|
}
|
|
if size.is_none() || (count(&style) + count(&weight) + count(&variant) + nb_normals) > 3 {
|
|
return Err(())
|
|
}
|
|
let mut copied_iter = iter.clone();
|
|
match copied_iter.next() {
|
|
Some(&Delim('/')) => {
|
|
iter = copied_iter;
|
|
line_height = match iter.next() {
|
|
Some(v) => line_height::from_component_value(v, base_url).ok(),
|
|
_ => return Err(()),
|
|
};
|
|
if line_height.is_none() { return Err(()) }
|
|
}
|
|
_ => ()
|
|
}
|
|
let family = try!(parse_comma_separated(
|
|
&mut BufferedIter::new(iter), font_family::parse_one_family));
|
|
Ok(Longhands {
|
|
font_style: style,
|
|
font_variant: variant,
|
|
font_weight: weight,
|
|
font_size: size,
|
|
line_height: line_height,
|
|
font_family: Some(family)
|
|
})
|
|
</%self:shorthand>
|
|
|
|
}
|
|
|
|
|
|
// TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template.
|
|
// Maybe submit for inclusion in libstd?
|
|
mod property_bit_field {
|
|
use std::uint;
|
|
use std::mem;
|
|
|
|
pub struct PropertyBitField {
|
|
storage: [uint, ..(${len(LONGHANDS)} - 1 + uint::BITS) / uint::BITS]
|
|
}
|
|
|
|
impl PropertyBitField {
|
|
#[inline]
|
|
pub fn new() -> PropertyBitField {
|
|
PropertyBitField { storage: unsafe { mem::zeroed() } }
|
|
}
|
|
|
|
#[inline]
|
|
fn get(&self, bit: uint) -> bool {
|
|
(self.storage[bit / uint::BITS] & (1 << (bit % uint::BITS))) != 0
|
|
}
|
|
#[inline]
|
|
fn set(&mut self, bit: uint) {
|
|
self.storage[bit / uint::BITS] |= 1 << (bit % uint::BITS)
|
|
}
|
|
#[inline]
|
|
fn clear(&mut self, bit: uint) {
|
|
self.storage[bit / uint::BITS] &= !(1 << (bit % uint::BITS))
|
|
}
|
|
% for i, property in enumerate(LONGHANDS):
|
|
#[allow(non_snake_case)]
|
|
#[inline]
|
|
pub fn get_${property.ident}(&self) -> bool {
|
|
self.get(${i})
|
|
}
|
|
#[allow(non_snake_case)]
|
|
#[inline]
|
|
pub fn set_${property.ident}(&mut self) {
|
|
self.set(${i})
|
|
}
|
|
#[allow(non_snake_case)]
|
|
#[inline]
|
|
pub fn clear_${property.ident}(&mut self) {
|
|
self.clear(${i})
|
|
}
|
|
% endfor
|
|
}
|
|
}
|
|
|
|
|
|
/// Declarations are stored in reverse order.
|
|
/// Overridden declarations are skipped.
|
|
pub struct PropertyDeclarationBlock {
|
|
pub important: Arc<Vec<PropertyDeclaration>>,
|
|
pub normal: Arc<Vec<PropertyDeclaration>>,
|
|
}
|
|
|
|
|
|
pub fn parse_style_attribute(input: &str, base_url: &Url) -> PropertyDeclarationBlock {
|
|
parse_property_declaration_list(tokenize(input), base_url)
|
|
}
|
|
|
|
|
|
pub fn parse_property_declaration_list<I: Iterator<Node>>(input: I, base_url: &Url) -> PropertyDeclarationBlock {
|
|
let mut important_declarations = vec!();
|
|
let mut normal_declarations = vec!();
|
|
let mut important_seen = PropertyBitField::new();
|
|
let mut normal_seen = PropertyBitField::new();
|
|
let items: Vec<DeclarationListItem> =
|
|
ErrorLoggerIterator(parse_declaration_list(input)).collect();
|
|
for item in items.into_iter().rev() {
|
|
match item {
|
|
DeclAtRule(rule) => log_css_error(
|
|
rule.location, format!("Unsupported at-rule in declaration list: @{:s}", rule.name).as_slice()),
|
|
Declaration_(Declaration{ location: l, name: n, value: v, important: i}) => {
|
|
// TODO: only keep the last valid declaration for a given name.
|
|
let (list, seen) = if i {
|
|
(&mut important_declarations, &mut important_seen)
|
|
} else {
|
|
(&mut normal_declarations, &mut normal_seen)
|
|
};
|
|
match PropertyDeclaration::parse(n.as_slice(), v.as_slice(), list, base_url, seen) {
|
|
UnknownProperty => log_css_error(l, format!(
|
|
"Unsupported property: {}:{}", n, v.iter().to_css()).as_slice()),
|
|
ExperimentalProperty => log_css_error(l, format!(
|
|
"Experimental property, use `servo --enable_experimental` \
|
|
or `servo -e` to enable: {}:{}",
|
|
n, v.iter().to_css()).as_slice()),
|
|
InvalidValue => log_css_error(l, format!(
|
|
"Invalid value: {}:{}", n, v.iter().to_css()).as_slice()),
|
|
ValidOrIgnoredDeclaration => (),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
PropertyDeclarationBlock {
|
|
important: Arc::new(important_declarations),
|
|
normal: Arc::new(normal_declarations),
|
|
}
|
|
}
|
|
|
|
|
|
pub enum CSSWideKeyword {
|
|
InitialKeyword,
|
|
InheritKeyword,
|
|
UnsetKeyword,
|
|
}
|
|
|
|
impl CSSWideKeyword {
|
|
pub fn parse(input: &[ComponentValue]) -> Result<CSSWideKeyword, ()> {
|
|
one_component_value(input).and_then(get_ident_lower).and_then(|keyword| {
|
|
match keyword.as_slice() {
|
|
"initial" => Ok(InitialKeyword),
|
|
"inherit" => Ok(InheritKeyword),
|
|
"unset" => Ok(UnsetKeyword),
|
|
_ => Err(())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
#[deriving(Clone)]
|
|
pub enum DeclaredValue<T> {
|
|
SpecifiedValue(T),
|
|
Initial,
|
|
Inherit,
|
|
// There is no Unset variant here.
|
|
// The 'unset' keyword is represented as either Initial or Inherit,
|
|
// depending on whether the property is inherited.
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
pub enum PropertyDeclaration {
|
|
% for property in LONGHANDS:
|
|
${property.camel_case}Declaration(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
|
|
% endfor
|
|
}
|
|
|
|
|
|
pub enum PropertyDeclarationParseResult {
|
|
UnknownProperty,
|
|
ExperimentalProperty,
|
|
InvalidValue,
|
|
ValidOrIgnoredDeclaration,
|
|
}
|
|
|
|
|
|
impl PropertyDeclaration {
|
|
pub fn parse(name: &str, value: &[ComponentValue],
|
|
result_list: &mut Vec<PropertyDeclaration>,
|
|
base_url: &Url,
|
|
seen: &mut PropertyBitField) -> PropertyDeclarationParseResult {
|
|
// FIXME: local variable to work around Rust #10683
|
|
let name_lower = name.as_slice().to_ascii_lower();
|
|
match name_lower.as_slice() {
|
|
% for property in LONGHANDS:
|
|
% if property.derived_from is None:
|
|
"${property.name}" => {
|
|
% if property.experimental:
|
|
if !::servo_util::opts::experimental_enabled() {
|
|
return ExperimentalProperty
|
|
}
|
|
% endif
|
|
if seen.get_${property.ident}() {
|
|
return ValidOrIgnoredDeclaration
|
|
}
|
|
match longhands::${property.ident}::parse_declared(value, base_url) {
|
|
Ok(value) => {
|
|
seen.set_${property.ident}();
|
|
result_list.push(${property.camel_case}Declaration(value));
|
|
ValidOrIgnoredDeclaration
|
|
},
|
|
Err(()) => InvalidValue,
|
|
}
|
|
},
|
|
% else:
|
|
"${property.name}" => UnknownProperty,
|
|
% endif
|
|
% endfor
|
|
% for shorthand in SHORTHANDS:
|
|
"${shorthand.name}" => {
|
|
if ${" && ".join("seen.get_%s()" % sub_property.ident
|
|
for sub_property in shorthand.sub_properties)} {
|
|
return ValidOrIgnoredDeclaration
|
|
}
|
|
match CSSWideKeyword::parse(value) {
|
|
Ok(InheritKeyword) => {
|
|
% for sub_property in shorthand.sub_properties:
|
|
if !seen.get_${sub_property.ident}() {
|
|
seen.set_${sub_property.ident}();
|
|
result_list.push(
|
|
${sub_property.camel_case}Declaration(Inherit));
|
|
}
|
|
% endfor
|
|
ValidOrIgnoredDeclaration
|
|
},
|
|
Ok(InitialKeyword) => {
|
|
% for sub_property in shorthand.sub_properties:
|
|
if !seen.get_${sub_property.ident}() {
|
|
seen.set_${sub_property.ident}();
|
|
result_list.push(
|
|
${sub_property.camel_case}Declaration(Initial));
|
|
}
|
|
% endfor
|
|
ValidOrIgnoredDeclaration
|
|
},
|
|
Ok(UnsetKeyword) => {
|
|
% for sub_property in shorthand.sub_properties:
|
|
if !seen.get_${sub_property.ident}() {
|
|
seen.set_${sub_property.ident}();
|
|
result_list.push(${sub_property.camel_case}Declaration(
|
|
${"Inherit" if sub_property.style_struct.inherited else "Initial"}
|
|
));
|
|
}
|
|
% endfor
|
|
ValidOrIgnoredDeclaration
|
|
},
|
|
Err(()) => match shorthands::${shorthand.ident}::parse(value, base_url) {
|
|
Ok(result) => {
|
|
% for sub_property in shorthand.sub_properties:
|
|
if !seen.get_${sub_property.ident}() {
|
|
seen.set_${sub_property.ident}();
|
|
result_list.push(${sub_property.camel_case}Declaration(
|
|
match result.${sub_property.ident} {
|
|
Some(value) => SpecifiedValue(value),
|
|
None => Initial,
|
|
}
|
|
));
|
|
}
|
|
% endfor
|
|
ValidOrIgnoredDeclaration
|
|
},
|
|
Err(()) => InvalidValue,
|
|
}
|
|
}
|
|
},
|
|
% endfor
|
|
_ => UnknownProperty,
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub mod style_structs {
|
|
use super::longhands;
|
|
|
|
% for style_struct in STYLE_STRUCTS:
|
|
#[deriving(PartialEq, Clone)]
|
|
pub struct ${style_struct.name} {
|
|
% for longhand in style_struct.longhands:
|
|
pub ${longhand.ident}: longhands::${longhand.ident}::computed_value::T,
|
|
% endfor
|
|
}
|
|
% endfor
|
|
}
|
|
|
|
#[deriving(Clone)]
|
|
pub struct ComputedValues {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
|
|
% endfor
|
|
shareable: bool,
|
|
pub writing_mode: WritingMode,
|
|
}
|
|
|
|
impl ComputedValues {
|
|
/// Resolves the currentColor keyword.
|
|
/// Any color value form computed values (except for the 'color' property itself)
|
|
/// should go through this method.
|
|
///
|
|
/// Usage example:
|
|
/// let top_color = style.resolve_color(style.Border.border_top_color);
|
|
#[inline]
|
|
pub fn resolve_color(&self, color: computed::CSSColor) -> RGBA {
|
|
match color {
|
|
RGBAColor(rgba) => rgba,
|
|
CurrentColor => self.get_color().color,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn content_inline_size(&self) -> computed_values::LengthOrPercentageOrAuto {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.height } else { box_style.width }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn content_block_size(&self) -> computed_values::LengthOrPercentageOrAuto {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.width } else { box_style.height }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn min_inline_size(&self) -> computed_values::LengthOrPercentage {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.min_height } else { box_style.min_width }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn min_block_size(&self) -> computed_values::LengthOrPercentage {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.min_width } else { box_style.min_height }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn max_inline_size(&self) -> computed_values::LengthOrPercentageOrNone {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.max_height } else { box_style.max_width }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn max_block_size(&self) -> computed_values::LengthOrPercentageOrNone {
|
|
let box_style = self.get_box();
|
|
if self.writing_mode.is_vertical() { box_style.max_width } else { box_style.max_height }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn logical_padding(&self) -> LogicalMargin<computed_values::LengthOrPercentage> {
|
|
let padding_style = self.get_padding();
|
|
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
|
padding_style.padding_top,
|
|
padding_style.padding_right,
|
|
padding_style.padding_bottom,
|
|
padding_style.padding_left,
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn logical_border_width(&self) -> LogicalMargin<Au> {
|
|
let border_style = self.get_border();
|
|
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
|
border_style.border_top_width,
|
|
border_style.border_right_width,
|
|
border_style.border_bottom_width,
|
|
border_style.border_left_width,
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn logical_margin(&self) -> LogicalMargin<computed_values::LengthOrPercentageOrAuto> {
|
|
let margin_style = self.get_margin();
|
|
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
|
margin_style.margin_top,
|
|
margin_style.margin_right,
|
|
margin_style.margin_bottom,
|
|
margin_style.margin_left,
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn logical_position(&self) -> LogicalMargin<computed_values::LengthOrPercentageOrAuto> {
|
|
// FIXME(SimonSapin): should be the writing mode of the containing block, maybe?
|
|
let position_style = self.get_positionoffsets();
|
|
LogicalMargin::from_physical(self.writing_mode, SideOffsets2D::new(
|
|
position_style.top,
|
|
position_style.right,
|
|
position_style.bottom,
|
|
position_style.left,
|
|
))
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_font_arc(&self) -> Arc<style_structs::Font> {
|
|
self.font.clone()
|
|
}
|
|
|
|
% for style_struct in STYLE_STRUCTS:
|
|
#[inline]
|
|
pub fn get_${style_struct.name.lower()}
|
|
<'a>(&'a self) -> &'a style_structs::${style_struct.name} {
|
|
&*self.${style_struct.ident}
|
|
}
|
|
% endfor
|
|
}
|
|
|
|
|
|
/// Return a WritingMode bitflags from the relevant CSS properties.
|
|
fn get_writing_mode(inheritedbox_style: &style_structs::InheritedBox) -> WritingMode {
|
|
use servo_util::logical_geometry;
|
|
let mut flags = WritingMode::empty();
|
|
match inheritedbox_style.direction {
|
|
computed_values::direction::ltr => {},
|
|
computed_values::direction::rtl => {
|
|
flags.insert(logical_geometry::FlagRTL);
|
|
},
|
|
}
|
|
match inheritedbox_style.writing_mode {
|
|
computed_values::writing_mode::horizontal_tb => {},
|
|
computed_values::writing_mode::vertical_rl => {
|
|
flags.insert(logical_geometry::FlagVertical);
|
|
},
|
|
computed_values::writing_mode::vertical_lr => {
|
|
flags.insert(logical_geometry::FlagVertical);
|
|
flags.insert(logical_geometry::FlagVerticalLR);
|
|
},
|
|
}
|
|
match inheritedbox_style.text_orientation {
|
|
computed_values::text_orientation::sideways_right => {},
|
|
computed_values::text_orientation::sideways_left => {
|
|
flags.insert(logical_geometry::FlagSidewaysLeft);
|
|
},
|
|
computed_values::text_orientation::sideways => {
|
|
if flags.intersects(logical_geometry::FlagVerticalLR) {
|
|
flags.insert(logical_geometry::FlagSidewaysLeft);
|
|
}
|
|
},
|
|
}
|
|
flags
|
|
}
|
|
|
|
|
|
/// The initial values for all style structs as defined by the specification.
|
|
lazy_static! {
|
|
static ref INITIAL_VALUES: ComputedValues = ComputedValues {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
${style_struct.ident}: Arc::new(style_structs::${style_struct.name} {
|
|
% for longhand in style_struct.longhands:
|
|
${longhand.ident}: longhands::${longhand.ident}::get_initial_value(),
|
|
% endfor
|
|
}),
|
|
% endfor
|
|
shareable: true,
|
|
writing_mode: WritingMode::empty()
|
|
};
|
|
}
|
|
|
|
|
|
#[test]
|
|
fn initial_writing_mode_is_empty() {
|
|
assert_eq!(get_writing_mode(INITIAL_VALUES.get_inheritedbox()), WritingMode::empty())
|
|
}
|
|
|
|
|
|
/// This only exists to limit the scope of #[allow(experimental)]
|
|
/// FIXME: remove this when Arc::make_unique() is not experimental anymore.
|
|
trait ArcExperimental<T> {
|
|
fn make_unique_experimental<'a>(&'a mut self) -> &'a mut T;
|
|
}
|
|
impl<T: Send + Sync + Clone> ArcExperimental<T> for Arc<T> {
|
|
#[inline]
|
|
#[allow(experimental)]
|
|
fn make_unique_experimental<'a>(&'a mut self) -> &'a mut T {
|
|
self.make_unique()
|
|
}
|
|
}
|
|
|
|
/// Fast path for the function below. Only computes new inherited styles.
|
|
#[allow(unused_mut)]
|
|
fn cascade_with_cached_declarations(applicable_declarations: &[DeclarationBlock],
|
|
shareable: bool,
|
|
parent_style: &ComputedValues,
|
|
cached_style: &ComputedValues,
|
|
context: &computed::Context)
|
|
-> ComputedValues {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
% if style_struct.inherited:
|
|
let mut style_${style_struct.ident} = parent_style.${style_struct.ident}.clone();
|
|
% else:
|
|
let mut style_${style_struct.ident} = cached_style.${style_struct.ident}.clone();
|
|
% endif
|
|
% endfor
|
|
|
|
let mut seen = PropertyBitField::new();
|
|
// Declaration blocks are stored in increasing precedence order,
|
|
// we want them in decreasing order here.
|
|
for sub_list in applicable_declarations.iter().rev() {
|
|
// Declarations are already stored in reverse order.
|
|
for declaration in sub_list.declarations.iter() {
|
|
match *declaration {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
% for property in style_struct.longhands:
|
|
% if property.derived_from is None:
|
|
${property.camel_case}Declaration(ref ${'_' if not style_struct.inherited else ''}declared_value) => {
|
|
% if style_struct.inherited:
|
|
if seen.get_${property.ident}() {
|
|
continue
|
|
}
|
|
seen.set_${property.ident}();
|
|
let computed_value = match *declared_value {
|
|
SpecifiedValue(ref specified_value)
|
|
=> longhands::${property.ident}::to_computed_value(
|
|
(*specified_value).clone(),
|
|
context
|
|
),
|
|
Initial
|
|
=> longhands::${property.ident}::get_initial_value(),
|
|
Inherit => {
|
|
// This is a bit slow, but this is rare so it shouldn't
|
|
// matter.
|
|
//
|
|
// FIXME: is it still?
|
|
parent_style.${style_struct.ident}
|
|
.${property.ident}
|
|
.clone()
|
|
}
|
|
};
|
|
style_${style_struct.ident}.make_unique_experimental()
|
|
.${property.ident} = computed_value;
|
|
% endif
|
|
|
|
% if property.name in DERIVED_LONGHANDS:
|
|
% if not style_struct.inherited:
|
|
// Use the cached value.
|
|
let computed_value = style_${style_struct.ident}
|
|
.${property.ident}.clone();
|
|
% endif
|
|
% for derived in DERIVED_LONGHANDS[property.name]:
|
|
style_${derived.style_struct.ident}
|
|
.make_unique_experimental()
|
|
.${derived.ident} =
|
|
longhands::${derived.ident}
|
|
::derive_from_${property.ident}(
|
|
computed_value,
|
|
context);
|
|
% endfor
|
|
% endif
|
|
}
|
|
% else:
|
|
${property.camel_case}Declaration(_) => {
|
|
// Do not allow stylesheets to set derived properties.
|
|
}
|
|
% endif
|
|
% endfor
|
|
% endfor
|
|
}
|
|
}
|
|
}
|
|
|
|
ComputedValues {
|
|
writing_mode: get_writing_mode(&*style_inheritedbox),
|
|
% for style_struct in STYLE_STRUCTS:
|
|
${style_struct.ident}: style_${style_struct.ident},
|
|
% endfor
|
|
shareable: shareable,
|
|
}
|
|
}
|
|
|
|
/// Performs the CSS cascade, computing new styles for an element from its parent style and
|
|
/// optionally a cached related style. The arguments are:
|
|
///
|
|
/// * `applicable_declarations`: The list of CSS rules that matched.
|
|
///
|
|
/// * `shareable`: Whether the `ComputedValues` structure to be constructed should be considered
|
|
/// shareable.
|
|
///
|
|
/// * `parent_style`: The parent style, if applicable; if `None`, this is the root node.
|
|
///
|
|
/// * `cached_style`: If present, cascading is short-circuited for everything but inherited
|
|
/// values and these values are used instead. Obviously, you must be careful when supplying
|
|
/// this that it is safe to only provide inherited declarations. If `parent_style` is `None`,
|
|
/// this is ignored.
|
|
///
|
|
/// Returns the computed values and a boolean indicating whether the result is cacheable.
|
|
pub fn cascade(applicable_declarations: &[DeclarationBlock],
|
|
shareable: bool,
|
|
parent_style: Option< &ComputedValues >,
|
|
cached_style: Option< &ComputedValues >)
|
|
-> (ComputedValues, bool) {
|
|
let initial_values = &*INITIAL_VALUES;
|
|
let (is_root_element, inherited_style) = match parent_style {
|
|
Some(parent_style) => (false, parent_style),
|
|
None => (true, initial_values),
|
|
};
|
|
|
|
let mut context = {
|
|
let inherited_font_style = inherited_style.get_font();
|
|
computed::Context {
|
|
is_root_element: is_root_element,
|
|
inherited_font_weight: inherited_font_style.font_weight,
|
|
inherited_font_size: inherited_font_style.font_size,
|
|
inherited_height: inherited_style.get_box().height,
|
|
inherited_text_decorations_in_effect:
|
|
inherited_style.get_inheritedtext()._servo_text_decorations_in_effect,
|
|
// To be overridden by applicable declarations:
|
|
font_size: inherited_font_style.font_size,
|
|
display: longhands::display::get_initial_value(),
|
|
color: inherited_style.get_color().color,
|
|
text_decoration: longhands::text_decoration::get_initial_value(),
|
|
positioned: false,
|
|
floated: false,
|
|
border_top_present: false,
|
|
border_right_present: false,
|
|
border_bottom_present: false,
|
|
border_left_present: false,
|
|
}
|
|
};
|
|
|
|
// This assumes that the computed and specified values have the same Rust type.
|
|
macro_rules! get_specified(
|
|
($style_struct_getter: ident, $property: ident, $declared_value: expr) => {
|
|
match *$declared_value {
|
|
SpecifiedValue(specified_value) => specified_value,
|
|
Initial => longhands::$property::get_initial_value(),
|
|
Inherit => inherited_style.$style_struct_getter().$property.clone(),
|
|
}
|
|
};
|
|
)
|
|
|
|
// Initialize `context`
|
|
// Declarations blocks are already stored in increasing precedence order.
|
|
for sub_list in applicable_declarations.iter() {
|
|
// Declarations are stored in reverse source order, we want them in forward order here.
|
|
for declaration in sub_list.declarations.iter().rev() {
|
|
match *declaration {
|
|
FontSizeDeclaration(ref value) => {
|
|
context.font_size = match *value {
|
|
SpecifiedValue(specified_value) => computed::compute_Au_with_font_size(
|
|
specified_value, context.inherited_font_size),
|
|
Initial => longhands::font_size::get_initial_value(),
|
|
Inherit => context.inherited_font_size,
|
|
}
|
|
}
|
|
ColorDeclaration(ref value) => {
|
|
context.color = get_specified!(get_color, color, value);
|
|
}
|
|
DisplayDeclaration(ref value) => {
|
|
context.display = get_specified!(get_box, display, value);
|
|
}
|
|
PositionDeclaration(ref value) => {
|
|
context.positioned = match get_specified!(get_box, position, value) {
|
|
longhands::position::absolute | longhands::position::fixed => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
FloatDeclaration(ref value) => {
|
|
context.floated = get_specified!(get_box, float, value)
|
|
!= longhands::float::none;
|
|
}
|
|
TextDecorationDeclaration(ref value) => {
|
|
context.text_decoration = get_specified!(get_text, text_decoration, value);
|
|
}
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
Border${side.capitalize()}StyleDeclaration(ref value) => {
|
|
context.border_${side}_present =
|
|
match get_specified!(get_border, border_${side}_style, value) {
|
|
longhands::border_top_style::none |
|
|
longhands::border_top_style::hidden => false,
|
|
_ => true,
|
|
};
|
|
}
|
|
% endfor
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
match (cached_style, parent_style) {
|
|
(Some(cached_style), Some(parent_style)) => {
|
|
return (cascade_with_cached_declarations(applicable_declarations,
|
|
shareable,
|
|
parent_style,
|
|
cached_style,
|
|
&context), false)
|
|
}
|
|
(_, _) => {}
|
|
}
|
|
|
|
// Set computed values, overwriting earlier declarations for the same property.
|
|
% for style_struct in STYLE_STRUCTS:
|
|
let mut style_${style_struct.ident} =
|
|
% if style_struct.inherited:
|
|
inherited_style
|
|
% else:
|
|
initial_values
|
|
% endif
|
|
.${style_struct.ident}.clone();
|
|
% endfor
|
|
let mut cacheable = true;
|
|
let mut seen = PropertyBitField::new();
|
|
// Declaration blocks are stored in increasing precedence order,
|
|
// we want them in decreasing order here.
|
|
for sub_list in applicable_declarations.iter().rev() {
|
|
// Declarations are already stored in reverse order.
|
|
for declaration in sub_list.declarations.iter() {
|
|
match *declaration {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
% for property in style_struct.longhands:
|
|
% if property.derived_from is None:
|
|
${property.camel_case}Declaration(ref declared_value) => {
|
|
if seen.get_${property.ident}() {
|
|
continue
|
|
}
|
|
seen.set_${property.ident}();
|
|
let computed_value = match *declared_value {
|
|
SpecifiedValue(ref specified_value)
|
|
=> longhands::${property.ident}::to_computed_value(
|
|
(*specified_value).clone(),
|
|
&context
|
|
),
|
|
Initial
|
|
=> longhands::${property.ident}::get_initial_value(),
|
|
Inherit => {
|
|
// This is a bit slow, but this is rare so it shouldn't
|
|
// matter.
|
|
//
|
|
// FIXME: is it still?
|
|
cacheable = false;
|
|
inherited_style.${style_struct.ident}
|
|
.${property.ident}
|
|
.clone()
|
|
}
|
|
};
|
|
style_${style_struct.ident}.make_unique_experimental()
|
|
.${property.ident} = computed_value;
|
|
|
|
% if property.name in DERIVED_LONGHANDS:
|
|
% for derived in DERIVED_LONGHANDS[property.name]:
|
|
style_${derived.style_struct.ident}
|
|
.make_unique_experimental()
|
|
.${derived.ident} =
|
|
longhands::${derived.ident}
|
|
::derive_from_${property.ident}(
|
|
computed_value,
|
|
&context);
|
|
% endfor
|
|
% endif
|
|
}
|
|
% else:
|
|
${property.camel_case}Declaration(_) => {
|
|
// Do not allow stylesheets to set derived properties.
|
|
}
|
|
% endif
|
|
% endfor
|
|
% endfor
|
|
}
|
|
}
|
|
}
|
|
|
|
// The initial value of border-*-width may be changed at computed value time.
|
|
{
|
|
let border = style_border.make_unique_experimental();
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
// Like calling to_computed_value, which wouldn't type check.
|
|
if !context.border_${side}_present {
|
|
border.border_${side}_width = Au(0);
|
|
}
|
|
% endfor
|
|
}
|
|
|
|
// The initial value of display may be changed at computed value time.
|
|
if !seen.get_display() {
|
|
let box_ = style_box_.make_unique_experimental();
|
|
box_.display = longhands::display::to_computed_value(box_.display, &context);
|
|
}
|
|
|
|
(ComputedValues {
|
|
writing_mode: get_writing_mode(&*style_inheritedbox),
|
|
% for style_struct in STYLE_STRUCTS:
|
|
${style_struct.ident}: style_${style_struct.ident},
|
|
% endfor
|
|
shareable: shareable,
|
|
}, cacheable)
|
|
}
|
|
|
|
|
|
/// Equivalent to `cascade()` with an empty `applicable_declarations`
|
|
/// Performs the CSS cascade for an anonymous box.
|
|
///
|
|
/// * `parent_style`: Computed style of the element this anonymous box inherits from.
|
|
pub fn cascade_anonymous(parent_style: &ComputedValues) -> ComputedValues {
|
|
let initial_values = &*INITIAL_VALUES;
|
|
let mut result = ComputedValues {
|
|
% for style_struct in STYLE_STRUCTS:
|
|
${style_struct.ident}:
|
|
% if style_struct.inherited:
|
|
parent_style
|
|
% else:
|
|
initial_values
|
|
% endif
|
|
.${style_struct.ident}.clone(),
|
|
% endfor
|
|
shareable: false,
|
|
writing_mode: parent_style.writing_mode,
|
|
};
|
|
{
|
|
let border = result.border.make_unique_experimental();
|
|
% for side in ["top", "right", "bottom", "left"]:
|
|
// Like calling to_computed_value, which wouldn't type check.
|
|
border.border_${side}_width = Au(0);
|
|
% endfor
|
|
}
|
|
// None of the teaks on 'display' apply here.
|
|
result
|
|
}
|
|
|
|
|
|
// Only re-export the types for computed values.
|
|
pub mod computed_values {
|
|
% for property in LONGHANDS:
|
|
pub use super::longhands::${property.ident}::computed_value as ${property.ident};
|
|
% endfor
|
|
// Don't use a side-specific name needlessly:
|
|
pub use super::longhands::border_top_style::computed_value as border_style;
|
|
|
|
pub use cssparser::RGBA;
|
|
pub use super::common_types::computed::{
|
|
LengthOrPercentage, LP_Length, LP_Percentage,
|
|
LengthOrPercentageOrAuto, LPA_Length, LPA_Percentage, LPA_Auto,
|
|
LengthOrPercentageOrNone, LPN_Length, LPN_Percentage, LPN_None};
|
|
}
|
|
|