mirror of
https://github.com/servo/servo.git
synced 2025-06-10 17:43:16 +00:00
1029 lines
38 KiB
Rust
1029 lines
38 KiB
Rust
/* 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/. */
|
||
|
||
#![allow(non_camel_case_types)]
|
||
|
||
pub use cssparser::RGBA;
|
||
|
||
|
||
macro_rules! define_css_keyword_enum {
|
||
($name: ident: $( $css: expr => $variant: ident ),+,) => {
|
||
define_css_keyword_enum!($name: $( $css => $variant ),+);
|
||
};
|
||
($name: ident: $( $css: expr => $variant: ident ),+) => {
|
||
#[allow(non_camel_case_types)]
|
||
#[derive(Clone, Eq, PartialEq, FromPrimitive, Copy)]
|
||
pub enum $name {
|
||
$( $variant ),+
|
||
}
|
||
|
||
impl $name {
|
||
pub fn parse(input: &mut ::cssparser::Parser) -> Result<$name, ()> {
|
||
match_ignore_ascii_case! { try!(input.expect_ident()),
|
||
$( $css => Ok($name::$variant) ),+
|
||
_ => Err(())
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ::std::fmt::Debug for $name {
|
||
#[inline]
|
||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||
use cssparser::ToCss;
|
||
self.fmt_to_css(f)
|
||
}
|
||
}
|
||
|
||
impl ::cssparser::ToCss for $name {
|
||
fn to_css<W>(&self, dest: &mut W) -> ::text_writer::Result
|
||
where W: ::text_writer::TextWriter {
|
||
match self {
|
||
$( &$name::$variant => dest.write_str($css) ),+
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
pub type CSSFloat = f64;
|
||
|
||
|
||
pub mod specified {
|
||
use std::ascii::AsciiExt;
|
||
use std::cmp;
|
||
use std::f64::consts::PI;
|
||
use std::fmt;
|
||
use std::fmt::{Formatter, Debug};
|
||
use std::num::{NumCast, ToPrimitive};
|
||
use url::Url;
|
||
use cssparser::{self, Token, Parser, ToCss, CssStringWriter};
|
||
use geom::size::Size2D;
|
||
use parser::ParserContext;
|
||
use text_writer::{self, TextWriter};
|
||
use util::geometry::Au;
|
||
use super::CSSFloat;
|
||
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct CSSColor {
|
||
pub parsed: cssparser::Color,
|
||
pub authored: Option<String>,
|
||
}
|
||
impl CSSColor {
|
||
pub fn parse(input: &mut Parser) -> Result<CSSColor, ()> {
|
||
let start_position = input.position();
|
||
let authored = match input.next() {
|
||
Ok(Token::Ident(s)) => Some(s.into_owned()),
|
||
_ => None,
|
||
};
|
||
input.reset(start_position);
|
||
Ok(CSSColor {
|
||
parsed: try!(cssparser::Color::parse(input)),
|
||
authored: authored,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl fmt::Debug for CSSColor {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for CSSColor {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self.authored {
|
||
Some(ref s) => dest.write_str(s),
|
||
None => self.parsed.to_css(dest),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct CSSRGBA {
|
||
pub parsed: cssparser::RGBA,
|
||
pub authored: Option<String>,
|
||
}
|
||
impl fmt::Debug for CSSRGBA {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for CSSRGBA {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self.authored {
|
||
Some(ref s) => dest.write_str(s),
|
||
None => self.parsed.to_css(dest),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum FontRelativeLength {
|
||
Em(CSSFloat),
|
||
Ex(CSSFloat),
|
||
Rem(CSSFloat)
|
||
}
|
||
|
||
impl fmt::Debug for FontRelativeLength {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for FontRelativeLength {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&FontRelativeLength::Em(length) => write!(dest, "{}em", length),
|
||
&FontRelativeLength::Ex(length) => write!(dest, "{}ex", length),
|
||
&FontRelativeLength::Rem(length) => write!(dest, "{}rem", length)
|
||
}
|
||
}
|
||
}
|
||
|
||
impl FontRelativeLength {
|
||
pub fn to_computed_value(&self,
|
||
reference_font_size: Au,
|
||
root_font_size: Au)
|
||
-> Au
|
||
{
|
||
match self {
|
||
&FontRelativeLength::Em(length) => reference_font_size.scale_by(length),
|
||
&FontRelativeLength::Ex(length) => {
|
||
let x_height = 0.5; // TODO: find that from the font
|
||
reference_font_size.scale_by(length * x_height)
|
||
},
|
||
&FontRelativeLength::Rem(length) => root_font_size.scale_by(length)
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum ViewportPercentageLength {
|
||
Vw(CSSFloat),
|
||
Vh(CSSFloat),
|
||
Vmin(CSSFloat),
|
||
Vmax(CSSFloat)
|
||
}
|
||
|
||
impl fmt::Debug for ViewportPercentageLength {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for ViewportPercentageLength {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&ViewportPercentageLength::Vw(length) => write!(dest, "{}vw", length),
|
||
&ViewportPercentageLength::Vh(length) => write!(dest, "{}vh", length),
|
||
&ViewportPercentageLength::Vmin(length) => write!(dest, "{}vmin", length),
|
||
&ViewportPercentageLength::Vmax(length) => write!(dest, "{}vmax", length)
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ViewportPercentageLength {
|
||
pub fn to_computed_value(&self, viewport_size: Size2D<Au>) -> Au {
|
||
macro_rules! to_unit {
|
||
($viewport_dimension:expr) => {
|
||
$viewport_dimension.to_f64().unwrap() / 100.0
|
||
}
|
||
}
|
||
|
||
let value = match self {
|
||
&ViewportPercentageLength::Vw(length) =>
|
||
length * to_unit!(viewport_size.width),
|
||
&ViewportPercentageLength::Vh(length) =>
|
||
length * to_unit!(viewport_size.height),
|
||
&ViewportPercentageLength::Vmin(length) =>
|
||
length * to_unit!(cmp::min(viewport_size.width, viewport_size.height)),
|
||
&ViewportPercentageLength::Vmax(length) =>
|
||
length * to_unit!(cmp::max(viewport_size.width, viewport_size.height)),
|
||
};
|
||
NumCast::from(value).unwrap()
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub struct CharacterWidth(pub i32);
|
||
|
||
impl CharacterWidth {
|
||
pub fn to_computed_value(&self, reference_font_size: Au) -> Au {
|
||
// This applies the *converting a character width to pixels* algorithm as specified
|
||
// in HTML5 § 14.5.4.
|
||
//
|
||
// TODO(pcwalton): Find these from the font.
|
||
let average_advance = reference_font_size.scale_by(0.5);
|
||
let max_advance = reference_font_size;
|
||
average_advance.scale_by(self.0 as CSSFloat - 1.0) + max_advance
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum Length {
|
||
Absolute(Au), // application units
|
||
FontRelative(FontRelativeLength),
|
||
ViewportPercentage(ViewportPercentageLength),
|
||
|
||
/// HTML5 "character width", as defined in HTML5 § 14.5.4.
|
||
///
|
||
/// This cannot be specified by the user directly and is only generated by
|
||
/// `Stylist::synthesize_rules_for_legacy_attributes()`.
|
||
ServoCharacterWidth(CharacterWidth),
|
||
}
|
||
|
||
impl fmt::Debug for Length {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for Length {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&Length::Absolute(length) => write!(dest, "{}px", length.to_subpx()),
|
||
&Length::FontRelative(length) => length.to_css(dest),
|
||
&Length::ViewportPercentage(length) => length.to_css(dest),
|
||
&Length::ServoCharacterWidth(_)
|
||
=> panic!("internal CSS values should never be serialized"),
|
||
}
|
||
}
|
||
}
|
||
|
||
const AU_PER_PX: CSSFloat = 60.;
|
||
const AU_PER_IN: CSSFloat = AU_PER_PX * 96.;
|
||
const AU_PER_CM: CSSFloat = AU_PER_IN / 2.54;
|
||
const AU_PER_MM: CSSFloat = AU_PER_IN / 25.4;
|
||
const AU_PER_PT: CSSFloat = AU_PER_IN / 72.;
|
||
const AU_PER_PC: CSSFloat = AU_PER_PT * 12.;
|
||
impl Length {
|
||
#[inline]
|
||
fn parse_internal(input: &mut Parser, negative_ok: bool) -> Result<Length, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
|
||
Length::parse_dimension(value.value, unit)
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => Ok(Length::Absolute(Au(0))),
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[allow(dead_code)]
|
||
pub fn parse(input: &mut Parser) -> Result<Length, ()> {
|
||
Length::parse_internal(input, /* negative_ok = */ true)
|
||
}
|
||
pub fn parse_non_negative(input: &mut Parser) -> Result<Length, ()> {
|
||
Length::parse_internal(input, /* negative_ok = */ false)
|
||
}
|
||
pub fn parse_dimension(value: CSSFloat, unit: &str) -> Result<Length, ()> {
|
||
match_ignore_ascii_case! { unit,
|
||
"px" => Ok(Length::from_px(value)),
|
||
"in" => Ok(Length::Absolute(Au((value * AU_PER_IN) as i32))),
|
||
"cm" => Ok(Length::Absolute(Au((value * AU_PER_CM) as i32))),
|
||
"mm" => Ok(Length::Absolute(Au((value * AU_PER_MM) as i32))),
|
||
"pt" => Ok(Length::Absolute(Au((value * AU_PER_PT) as i32))),
|
||
"pc" => Ok(Length::Absolute(Au((value * AU_PER_PC) as i32))),
|
||
// font-relative
|
||
"em" => Ok(Length::FontRelative(FontRelativeLength::Em(value))),
|
||
"ex" => Ok(Length::FontRelative(FontRelativeLength::Ex(value))),
|
||
"rem" => Ok(Length::FontRelative(FontRelativeLength::Rem(value))),
|
||
// viewport percentages
|
||
"vw" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vw(value))),
|
||
"vh" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vh(value))),
|
||
"vmin" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vmin(value))),
|
||
"vmax" => Ok(Length::ViewportPercentage(ViewportPercentageLength::Vmax(value)))
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[inline]
|
||
pub fn from_px(px_value: CSSFloat) -> Length {
|
||
Length::Absolute(Au((px_value * AU_PER_PX) as i32))
|
||
}
|
||
}
|
||
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum LengthOrPercentage {
|
||
Length(Length),
|
||
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
|
||
}
|
||
|
||
impl fmt::Debug for LengthOrPercentage {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for LengthOrPercentage {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&LengthOrPercentage::Length(length) => length.to_css(dest),
|
||
&LengthOrPercentage::Percentage(percentage)
|
||
=> write!(dest, "{}%", percentage * 100.),
|
||
}
|
||
}
|
||
}
|
||
impl LengthOrPercentage {
|
||
fn parse_internal(input: &mut Parser, negative_ok: bool)
|
||
-> Result<LengthOrPercentage, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
|
||
Length::parse_dimension(value.value, unit)
|
||
.map(LengthOrPercentage::Length)
|
||
}
|
||
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
|
||
Ok(LengthOrPercentage::Percentage(value.unit_value))
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => {
|
||
Ok(LengthOrPercentage::Length(Length::Absolute(Au(0))))
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[allow(dead_code)]
|
||
#[inline]
|
||
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentage, ()> {
|
||
LengthOrPercentage::parse_internal(input, /* negative_ok = */ true)
|
||
}
|
||
#[inline]
|
||
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentage, ()> {
|
||
LengthOrPercentage::parse_internal(input, /* negative_ok = */ false)
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum LengthOrPercentageOrAuto {
|
||
Length(Length),
|
||
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
|
||
Auto,
|
||
}
|
||
|
||
impl fmt::Debug for LengthOrPercentageOrAuto {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for LengthOrPercentageOrAuto {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&LengthOrPercentageOrAuto::Length(length) => length.to_css(dest),
|
||
&LengthOrPercentageOrAuto::Percentage(percentage)
|
||
=> write!(dest, "{}%", percentage * 100.),
|
||
&LengthOrPercentageOrAuto::Auto => dest.write_str("auto"),
|
||
}
|
||
}
|
||
}
|
||
impl LengthOrPercentageOrAuto {
|
||
fn parse_internal(input: &mut Parser, negative_ok: bool)
|
||
-> Result<LengthOrPercentageOrAuto, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
|
||
Length::parse_dimension(value.value, unit)
|
||
.map(LengthOrPercentageOrAuto::Length)
|
||
}
|
||
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
|
||
Ok(LengthOrPercentageOrAuto::Percentage(value.unit_value))
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => {
|
||
Ok(LengthOrPercentageOrAuto::Length(Length::Absolute(Au(0))))
|
||
}
|
||
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") => {
|
||
Ok(LengthOrPercentageOrAuto::Auto)
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[inline]
|
||
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ true)
|
||
}
|
||
#[inline]
|
||
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrAuto, ()> {
|
||
LengthOrPercentageOrAuto::parse_internal(input, /* negative_ok = */ false)
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum LengthOrPercentageOrNone {
|
||
Length(Length),
|
||
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
|
||
None,
|
||
}
|
||
|
||
impl fmt::Debug for LengthOrPercentageOrNone {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for LengthOrPercentageOrNone {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&LengthOrPercentageOrNone::Length(length) => length.to_css(dest),
|
||
&LengthOrPercentageOrNone::Percentage(percentage)
|
||
=> write!(dest, "{}%", percentage * 100.),
|
||
&LengthOrPercentageOrNone::None => dest.write_str("none"),
|
||
}
|
||
}
|
||
}
|
||
impl LengthOrPercentageOrNone {
|
||
fn parse_internal(input: &mut Parser, negative_ok: bool)
|
||
-> Result<LengthOrPercentageOrNone, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(ref value, ref unit) if negative_ok || value.value >= 0. => {
|
||
Length::parse_dimension(value.value, unit)
|
||
.map(LengthOrPercentageOrNone::Length)
|
||
}
|
||
Token::Percentage(ref value) if negative_ok || value.unit_value >= 0. => {
|
||
Ok(LengthOrPercentageOrNone::Percentage(value.unit_value))
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => {
|
||
Ok(LengthOrPercentageOrNone::Length(Length::Absolute(Au(0))))
|
||
}
|
||
Token::Ident(ref value) if value.eq_ignore_ascii_case("none") => {
|
||
Ok(LengthOrPercentageOrNone::None)
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[allow(dead_code)]
|
||
#[inline]
|
||
pub fn parse(input: &mut Parser) -> Result<LengthOrPercentageOrNone, ()> {
|
||
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ true)
|
||
}
|
||
#[inline]
|
||
pub fn parse_non_negative(input: &mut Parser) -> Result<LengthOrPercentageOrNone, ()> {
|
||
LengthOrPercentageOrNone::parse_internal(input, /* negative_ok = */ false)
|
||
}
|
||
}
|
||
|
||
// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum PositionComponent {
|
||
Length(Length),
|
||
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
|
||
Center,
|
||
Left,
|
||
Right,
|
||
Top,
|
||
Bottom,
|
||
}
|
||
impl PositionComponent {
|
||
pub fn parse(input: &mut Parser) -> Result<PositionComponent, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(ref value, ref unit) => {
|
||
Length::parse_dimension(value.value, unit)
|
||
.map(PositionComponent::Length)
|
||
}
|
||
Token::Percentage(ref value) => {
|
||
Ok(PositionComponent::Percentage(value.unit_value))
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => {
|
||
Ok(PositionComponent::Length(Length::Absolute(Au(0))))
|
||
}
|
||
Token::Ident(value) => {
|
||
match_ignore_ascii_case! { value,
|
||
"center" => Ok(PositionComponent::Center),
|
||
"left" => Ok(PositionComponent::Left),
|
||
"right" => Ok(PositionComponent::Right),
|
||
"top" => Ok(PositionComponent::Top),
|
||
"bottom" => Ok(PositionComponent::Bottom)
|
||
_ => Err(())
|
||
}
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
#[inline]
|
||
pub fn to_length_or_percentage(self) -> LengthOrPercentage {
|
||
match self {
|
||
PositionComponent::Length(x) => LengthOrPercentage::Length(x),
|
||
PositionComponent::Percentage(x) => LengthOrPercentage::Percentage(x),
|
||
PositionComponent::Center => LengthOrPercentage::Percentage(0.5),
|
||
PositionComponent::Left |
|
||
PositionComponent::Top => LengthOrPercentage::Percentage(0.0),
|
||
PositionComponent::Right |
|
||
PositionComponent::Bottom => LengthOrPercentage::Percentage(1.0),
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(Clone, PartialEq, PartialOrd, Copy)]
|
||
pub struct Angle(pub CSSFloat);
|
||
|
||
impl fmt::Debug for Angle {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for Angle {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
let Angle(value) = *self;
|
||
write!(dest, "{}rad", value)
|
||
}
|
||
}
|
||
|
||
impl Angle {
|
||
pub fn radians(self) -> f64 {
|
||
let Angle(radians) = self;
|
||
radians
|
||
}
|
||
}
|
||
|
||
const RAD_PER_DEG: CSSFloat = PI / 180.0;
|
||
const RAD_PER_GRAD: CSSFloat = PI / 200.0;
|
||
const RAD_PER_TURN: CSSFloat = PI * 2.0;
|
||
|
||
impl Angle {
|
||
/// Parses an angle according to CSS-VALUES § 6.1.
|
||
pub fn parse(input: &mut Parser) -> Result<Angle, ()> {
|
||
match try!(input.next()) {
|
||
Token::Dimension(value, unit) => {
|
||
match_ignore_ascii_case! { unit,
|
||
"deg" => Ok(Angle(value.value * RAD_PER_DEG)),
|
||
"grad" => Ok(Angle(value.value * RAD_PER_GRAD)),
|
||
"turn" => Ok(Angle(value.value * RAD_PER_TURN)),
|
||
"rad" => Ok(Angle(value.value))
|
||
_ => Err(())
|
||
}
|
||
}
|
||
Token::Number(ref value) if value.value == 0. => Ok(Angle(0.)),
|
||
_ => Err(())
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Specified values for an image according to CSS-IMAGES.
|
||
#[derive(Clone, PartialEq)]
|
||
pub enum Image {
|
||
Url(Url),
|
||
LinearGradient(LinearGradient),
|
||
}
|
||
|
||
impl fmt::Debug for Image {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for Image {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&Image::Url(ref url) => {
|
||
try!(dest.write_str("url(\""));
|
||
try!(write!(&mut CssStringWriter::new(dest), "{}", url));
|
||
try!(dest.write_str("\")"));
|
||
Ok(())
|
||
}
|
||
&Image::LinearGradient(ref gradient) => gradient.to_css(dest)
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Image {
|
||
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<Image, ()> {
|
||
match try!(input.next()) {
|
||
Token::Url(url) => {
|
||
Ok(Image::Url(context.parse_url(&url)))
|
||
}
|
||
Token::Function(name) => {
|
||
match_ignore_ascii_case! { name,
|
||
"linear-gradient" => {
|
||
Ok(Image::LinearGradient(try!(
|
||
input.parse_nested_block(LinearGradient::parse_function))))
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
_ => Err(())
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Specified values for a CSS linear gradient.
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct LinearGradient {
|
||
/// The angle or corner of the gradient.
|
||
pub angle_or_corner: AngleOrCorner,
|
||
|
||
/// The color stops.
|
||
pub stops: Vec<ColorStop>,
|
||
}
|
||
|
||
impl fmt::Debug for LinearGradient {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for LinearGradient {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
try!(dest.write_str("linear-gradient("));
|
||
try!(self.angle_or_corner.to_css(dest));
|
||
for stop in self.stops.iter() {
|
||
try!(dest.write_str(", "));
|
||
try!(stop.to_css(dest));
|
||
}
|
||
try!(dest.write_char(')'));
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// Specified values for an angle or a corner in a linear gradient.
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub enum AngleOrCorner {
|
||
Angle(Angle),
|
||
Corner(HorizontalDirection, VerticalDirection),
|
||
}
|
||
|
||
impl fmt::Debug for AngleOrCorner {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for AngleOrCorner {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
match self {
|
||
&AngleOrCorner::Angle(angle) => angle.to_css(dest),
|
||
&AngleOrCorner::Corner(horizontal, vertical) => {
|
||
try!(dest.write_str("to "));
|
||
try!(horizontal.to_css(dest));
|
||
try!(dest.write_char(' '));
|
||
try!(vertical.to_css(dest));
|
||
Ok(())
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Specified values for one color stop in a linear gradient.
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct ColorStop {
|
||
/// The color of this stop.
|
||
pub color: CSSColor,
|
||
|
||
/// The position of this stop. If not specified, this stop is placed halfway between the
|
||
/// point that precedes it and the point that follows it.
|
||
pub position: Option<LengthOrPercentage>,
|
||
}
|
||
|
||
impl fmt::Debug for ColorStop {
|
||
#[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_to_css(f) }
|
||
}
|
||
|
||
impl ToCss for ColorStop {
|
||
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
|
||
try!(self.color.to_css(dest));
|
||
if let Some(position) = self.position {
|
||
try!(dest.write_char(' '));
|
||
try!(position.to_css(dest));
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
define_css_keyword_enum!(HorizontalDirection: "left" => Left, "right" => Right);
|
||
define_css_keyword_enum!(VerticalDirection: "top" => Top, "bottom" => Bottom);
|
||
|
||
fn parse_one_color_stop(input: &mut Parser) -> Result<ColorStop, ()> {
|
||
Ok(ColorStop {
|
||
color: try!(CSSColor::parse(input)),
|
||
position: input.try(LengthOrPercentage::parse).ok(),
|
||
})
|
||
}
|
||
|
||
impl LinearGradient {
|
||
/// Parses a linear gradient from the given arguments.
|
||
pub fn parse_function(input: &mut Parser) -> Result<LinearGradient, ()> {
|
||
let angle_or_corner = if input.try(|input| input.expect_ident_matching("to")).is_ok() {
|
||
let (horizontal, vertical) =
|
||
if let Ok(value) = input.try(HorizontalDirection::parse) {
|
||
(Some(value), input.try(VerticalDirection::parse).ok())
|
||
} else {
|
||
let value = try!(VerticalDirection::parse(input));
|
||
(input.try(HorizontalDirection::parse).ok(), Some(value))
|
||
};
|
||
try!(input.expect_comma());
|
||
match (horizontal, vertical) {
|
||
(None, Some(VerticalDirection::Top)) => {
|
||
AngleOrCorner::Angle(Angle(0.0))
|
||
},
|
||
(Some(HorizontalDirection::Right), None) => {
|
||
AngleOrCorner::Angle(Angle(PI * 0.5))
|
||
},
|
||
(None, Some(VerticalDirection::Bottom)) => {
|
||
AngleOrCorner::Angle(Angle(PI))
|
||
},
|
||
(Some(HorizontalDirection::Left), None) => {
|
||
AngleOrCorner::Angle(Angle(PI * 1.5))
|
||
},
|
||
(Some(horizontal), Some(vertical)) => {
|
||
AngleOrCorner::Corner(horizontal, vertical)
|
||
}
|
||
(None, None) => unreachable!(),
|
||
}
|
||
} else if let Ok(angle) = input.try(Angle::parse) {
|
||
try!(input.expect_comma());
|
||
AngleOrCorner::Angle(angle)
|
||
} else {
|
||
AngleOrCorner::Angle(Angle(PI))
|
||
};
|
||
// Parse the color stops.
|
||
let stops = try!(input.parse_comma_separated(parse_one_color_stop));
|
||
if stops.len() < 2 {
|
||
return Err(())
|
||
}
|
||
Ok(LinearGradient {
|
||
angle_or_corner: angle_or_corner,
|
||
stops: stops,
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
pub fn parse_border_width(input: &mut Parser) -> Result<Length, ()> {
|
||
input.try(Length::parse_non_negative).or_else(|()| {
|
||
match_ignore_ascii_case! { try!(input.expect_ident()),
|
||
"thin" => Ok(Length::from_px(1.)),
|
||
"medium" => Ok(Length::from_px(3.)),
|
||
"thick" => Ok(Length::from_px(5.))
|
||
_ => Err(())
|
||
}
|
||
})
|
||
}
|
||
|
||
define_css_keyword_enum! { BorderStyle:
|
||
"none" => none,
|
||
"solid" => solid,
|
||
"double" => double,
|
||
"dotted" => dotted,
|
||
"dashed" => dashed,
|
||
"hidden" => hidden,
|
||
"groove" => groove,
|
||
"ridge" => ridge,
|
||
"inset" => inset,
|
||
"outset" => outset,
|
||
}
|
||
}
|
||
|
||
|
||
pub mod computed {
|
||
pub use super::specified::BorderStyle;
|
||
use super::specified::{AngleOrCorner};
|
||
use super::{specified, CSSFloat};
|
||
pub use cssparser::Color as CSSColor;
|
||
use geom::size::Size2D;
|
||
use properties::longhands;
|
||
use std::fmt;
|
||
use url::Url;
|
||
use util::geometry::Au;
|
||
|
||
#[allow(missing_copy_implementations)] // It’s kinda big
|
||
pub struct Context {
|
||
pub inherited_font_weight: longhands::font_weight::computed_value::T,
|
||
pub inherited_font_size: longhands::font_size::computed_value::T,
|
||
pub inherited_text_decorations_in_effect:
|
||
longhands::_servo_text_decorations_in_effect::computed_value::T,
|
||
pub inherited_height: longhands::height::computed_value::T,
|
||
pub color: longhands::color::computed_value::T,
|
||
pub text_decoration: longhands::text_decoration::computed_value::T,
|
||
pub font_size: longhands::font_size::computed_value::T,
|
||
pub root_font_size: longhands::font_size::computed_value::T,
|
||
pub display: longhands::display::computed_value::T,
|
||
pub overflow_x: longhands::overflow_x::computed_value::T,
|
||
pub overflow_y: longhands::overflow_y::computed_value::T,
|
||
pub positioned: bool,
|
||
pub floated: bool,
|
||
pub border_top_present: bool,
|
||
pub border_right_present: bool,
|
||
pub border_bottom_present: bool,
|
||
pub border_left_present: bool,
|
||
pub is_root_element: bool,
|
||
pub viewport_size: Size2D<Au>
|
||
// TODO, as needed: viewport size, etc.
|
||
}
|
||
|
||
pub trait ToComputedValue {
|
||
type ComputedValue;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue;
|
||
}
|
||
|
||
pub trait ComputedValueAsSpecified {}
|
||
|
||
impl<T> ToComputedValue for T where T: ComputedValueAsSpecified + Clone {
|
||
type ComputedValue = T;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, _context: &Context) -> T {
|
||
self.clone()
|
||
}
|
||
}
|
||
|
||
impl ToComputedValue for specified::CSSColor {
|
||
type ComputedValue = CSSColor;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, _context: &Context) -> CSSColor {
|
||
self.parsed
|
||
}
|
||
}
|
||
|
||
impl ComputedValueAsSpecified for specified::BorderStyle {}
|
||
|
||
impl ToComputedValue for specified::Length {
|
||
type ComputedValue = Au;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, context: &Context) -> Au {
|
||
match self {
|
||
&specified::Length::Absolute(length) => length,
|
||
&specified::Length::FontRelative(length) =>
|
||
length.to_computed_value(context.font_size, context.root_font_size),
|
||
&specified::Length::ViewportPercentage(length) =>
|
||
length.to_computed_value(context.viewport_size),
|
||
&specified::Length::ServoCharacterWidth(length) =>
|
||
length.to_computed_value(context.font_size)
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(PartialEq, Clone, Copy)]
|
||
pub enum LengthOrPercentage {
|
||
Length(Au),
|
||
Percentage(CSSFloat),
|
||
}
|
||
impl fmt::Debug for LengthOrPercentage {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
match self {
|
||
&LengthOrPercentage::Length(length) => write!(f, "{:?}", length),
|
||
&LengthOrPercentage::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ToComputedValue for specified::LengthOrPercentage {
|
||
type ComputedValue = LengthOrPercentage;
|
||
|
||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentage {
|
||
match *self {
|
||
specified::LengthOrPercentage::Length(value) => {
|
||
LengthOrPercentage::Length(value.to_computed_value(context))
|
||
}
|
||
specified::LengthOrPercentage::Percentage(value) => {
|
||
LengthOrPercentage::Percentage(value)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(PartialEq, Clone, Copy)]
|
||
pub enum LengthOrPercentageOrAuto {
|
||
Length(Au),
|
||
Percentage(CSSFloat),
|
||
Auto,
|
||
}
|
||
impl fmt::Debug for LengthOrPercentageOrAuto {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
match self {
|
||
&LengthOrPercentageOrAuto::Length(length) => write!(f, "{:?}", length),
|
||
&LengthOrPercentageOrAuto::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
|
||
&LengthOrPercentageOrAuto::Auto => write!(f, "auto"),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ToComputedValue for specified::LengthOrPercentageOrAuto {
|
||
type ComputedValue = LengthOrPercentageOrAuto;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrAuto {
|
||
match *self {
|
||
specified::LengthOrPercentageOrAuto::Length(value) => {
|
||
LengthOrPercentageOrAuto::Length(value.to_computed_value(context))
|
||
}
|
||
specified::LengthOrPercentageOrAuto::Percentage(value) => {
|
||
LengthOrPercentageOrAuto::Percentage(value)
|
||
}
|
||
specified::LengthOrPercentageOrAuto::Auto => {
|
||
LengthOrPercentageOrAuto::Auto
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[derive(PartialEq, Clone, Copy)]
|
||
pub enum LengthOrPercentageOrNone {
|
||
Length(Au),
|
||
Percentage(CSSFloat),
|
||
None,
|
||
}
|
||
impl fmt::Debug for LengthOrPercentageOrNone {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
match self {
|
||
&LengthOrPercentageOrNone::Length(length) => write!(f, "{:?}", length),
|
||
&LengthOrPercentageOrNone::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
|
||
&LengthOrPercentageOrNone::None => write!(f, "none"),
|
||
}
|
||
}
|
||
}
|
||
|
||
impl ToComputedValue for specified::LengthOrPercentageOrNone {
|
||
type ComputedValue = LengthOrPercentageOrNone;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, context: &Context) -> LengthOrPercentageOrNone {
|
||
match *self {
|
||
specified::LengthOrPercentageOrNone::Length(value) => {
|
||
LengthOrPercentageOrNone::Length(value.to_computed_value(context))
|
||
}
|
||
specified::LengthOrPercentageOrNone::Percentage(value) => {
|
||
LengthOrPercentageOrNone::Percentage(value)
|
||
}
|
||
specified::LengthOrPercentageOrNone::None => {
|
||
LengthOrPercentageOrNone::None
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
impl ToComputedValue for specified::Image {
|
||
type ComputedValue = Image;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, context: &Context) -> Image {
|
||
match *self {
|
||
specified::Image::Url(ref url) => Image::Url(url.clone()),
|
||
specified::Image::LinearGradient(ref linear_gradient) => {
|
||
Image::LinearGradient(linear_gradient.to_computed_value(context))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// Computed values for an image according to CSS-IMAGES.
|
||
#[derive(Clone, PartialEq)]
|
||
pub enum Image {
|
||
Url(Url),
|
||
LinearGradient(LinearGradient),
|
||
}
|
||
|
||
impl fmt::Debug for Image {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
match self {
|
||
&Image::Url(ref url) => write!(f, "url(\"{}\")", url),
|
||
&Image::LinearGradient(ref grad) => write!(f, "linear-gradient({:?})", grad),
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Computed values for a CSS linear gradient.
|
||
#[derive(Clone, PartialEq)]
|
||
pub struct LinearGradient {
|
||
/// The angle or corner of the gradient.
|
||
pub angle_or_corner: AngleOrCorner,
|
||
|
||
/// The color stops.
|
||
pub stops: Vec<ColorStop>,
|
||
}
|
||
|
||
impl fmt::Debug for LinearGradient {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
let _ = write!(f, "{:?}", self.angle_or_corner);
|
||
for stop in self.stops.iter() {
|
||
let _ = write!(f, ", {:?}", stop);
|
||
}
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// Computed values for one color stop in a linear gradient.
|
||
#[derive(Clone, PartialEq, Copy)]
|
||
pub struct ColorStop {
|
||
/// The color of this stop.
|
||
pub color: CSSColor,
|
||
|
||
/// The position of this stop. If not specified, this stop is placed halfway between the
|
||
/// point that precedes it and the point that follows it per CSS-IMAGES § 3.4.
|
||
pub position: Option<LengthOrPercentage>,
|
||
}
|
||
|
||
impl fmt::Debug for ColorStop {
|
||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||
let _ = write!(f, "{:?}", self.color);
|
||
self.position.map(|pos| {
|
||
let _ = write!(f, " {:?}", pos);
|
||
});
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
impl ToComputedValue for specified::LinearGradient {
|
||
type ComputedValue = LinearGradient;
|
||
|
||
#[inline]
|
||
fn to_computed_value(&self, context: &Context) -> LinearGradient {
|
||
let specified::LinearGradient {
|
||
angle_or_corner,
|
||
ref stops
|
||
} = *self;
|
||
LinearGradient {
|
||
angle_or_corner: angle_or_corner,
|
||
stops: stops.iter().map(|stop| {
|
||
ColorStop {
|
||
color: stop.color.parsed,
|
||
position: match stop.position {
|
||
None => None,
|
||
Some(value) => Some(value.to_computed_value(context)),
|
||
},
|
||
}
|
||
}).collect()
|
||
}
|
||
}
|
||
}
|
||
pub type Length = Au;
|
||
}
|