diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index 88413b350a8..4045c7b9799 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -474,16 +474,33 @@ pub struct GradientDisplayItem { /// Renders a border. #[deriving(Clone)] pub struct BorderDisplayItem { + /// Fields common to all display items. pub base: BaseDisplayItem, - /// The border widths - pub border: SideOffsets2D, + /// Border widths. + pub border_widths: SideOffsets2D, - /// The border colors. + /// Border colors. pub color: SideOffsets2D, - /// The border styles. - pub style: SideOffsets2D + /// Border styles. + pub style: SideOffsets2D, + + /// Border radii. + /// + /// TODO(pcwalton): Elliptical radii. + pub radius: BorderRadii, +} + +/// Information about the border radii. +/// +/// TODO(pcwalton): Elliptical radii. +#[deriving(Clone, Default, Show)] +pub struct BorderRadii { + pub top_left: T, + pub top_right: T, + pub bottom_right: T, + pub bottom_left: T, } /// Renders a line segment. @@ -565,7 +582,8 @@ impl DisplayItem { BorderDisplayItemClass(ref border) => { render_context.draw_border(&border.base.bounds, - border.border, + border.border_widths, + &border.radius, border.color, border.style) } @@ -658,4 +676,3 @@ impl OpaqueNodeMethods for OpaqueNode { } } } - diff --git a/components/gfx/render_context.rs b/components/gfx/render_context.rs index ea7771efb9d..973d1925a3f 100644 --- a/components/gfx/render_context.rs +++ b/components/gfx/render_context.rs @@ -10,7 +10,7 @@ use azure::azure_hl::{LinearGradientPattern, LinearGradientPatternRef, SourceOp, use azure::scaled_font::ScaledFont; use azure::{AZ_CAP_BUTT, AzFloat, struct__AzDrawOptions, struct__AzGlyph}; use azure::{struct__AzGlyphBuffer, struct__AzPoint, AzDrawTargetFillGlyphs}; -use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright}; +use display_list::{SidewaysLeft, SidewaysRight, TextDisplayItem, Upright, BorderRadii}; use font_context::FontContext; use geom::matrix2d::Matrix2D; use geom::point::Point2D; @@ -24,7 +24,8 @@ use servo_net::image::base::Image; use servo_util::geometry::Au; use servo_util::opts; use servo_util::range::Range; -use std::num::Zero; +use std::default::Default; +use std::num::{Float, FloatMath, Zero}; use std::ptr; use style::computed_values::border_style; use sync::Arc; @@ -67,15 +68,18 @@ impl<'a> RenderContext<'a> { pub fn draw_border(&self, bounds: &Rect, border: SideOffsets2D, + radius: &BorderRadii, color: SideOffsets2D, style: SideOffsets2D) { let border = border.to_float_px(); + let radius = radius.to_radii_px(); + self.draw_target.make_current(); - self.draw_border_segment(Top, bounds, border, color, style); - self.draw_border_segment(Right, bounds, border, color, style); - self.draw_border_segment(Bottom, bounds, border, color, style); - self.draw_border_segment(Left, bounds, border, color, style); + self.draw_border_segment(Top, bounds, border, &radius, color, style); + self.draw_border_segment(Right, bounds, border, &radius, color, style); + self.draw_border_segment(Bottom, bounds, border, &radius, color, style); + self.draw_border_segment(Left, bounds, border, &radius, color, style); } pub fn draw_line(&self, @@ -84,7 +88,7 @@ impl<'a> RenderContext<'a> { style: border_style::T) { self.draw_target.make_current(); - self.draw_line_segment(bounds, color, style); + self.draw_line_segment(bounds, &Default::default(), color, style); } pub fn draw_push_clip(&self, bounds: &Rect) { @@ -149,7 +153,7 @@ impl<'a> RenderContext<'a> { self.draw_target.fill_rect(&rect, ColorPatternRef(&pattern), Some(&draw_options)); } - fn draw_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, color: SideOffsets2D, style: SideOffsets2D) { + fn draw_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, radius: &BorderRadii, color: SideOffsets2D, style: SideOffsets2D) { let (style_select, color_select) = match direction { Top => (style.top, color.top), Left => (style.left, color.left), @@ -170,24 +174,24 @@ impl<'a> RenderContext<'a> { self.draw_dashed_border_segment(direction, bounds, border, color_select, DashedBorder); } border_style::solid => { - self.draw_solid_border_segment(direction,bounds,border,color_select); + self.draw_solid_border_segment(direction,bounds,border,radius,color_select); } border_style::double => { - self.draw_double_border_segment(direction, bounds, border, color_select); + self.draw_double_border_segment(direction, bounds, border, radius, color_select); } border_style::groove | border_style::ridge => { - self.draw_groove_ridge_border_segment(direction, bounds, border, color_select, style_select); + self.draw_groove_ridge_border_segment(direction, bounds, border, radius, color_select, style_select); } border_style::inset | border_style::outset => { - self.draw_inset_outset_border_segment(direction, bounds, border, style_select, color_select); + self.draw_inset_outset_border_segment(direction, bounds, border, radius, color_select, style_select); } } } - fn draw_line_segment(&self, bounds: &Rect, color: Color, style: border_style::T) { + fn draw_line_segment(&self, bounds: &Rect, radius: &BorderRadii, color: Color, style: border_style::T) { let border = SideOffsets2D::new_all_same(bounds.size.width).to_float_px(); - match style{ + match style { border_style::none | border_style::hidden => {} border_style::dotted => { self.draw_dashed_border_segment(Right, bounds, border, color, DottedBorder); @@ -196,61 +200,231 @@ impl<'a> RenderContext<'a> { self.draw_dashed_border_segment(Right, bounds, border, color, DashedBorder); } border_style::solid => { - self.draw_solid_border_segment(Right,bounds,border,color); + self.draw_solid_border_segment(Right, bounds, border, radius, color); } border_style::double => { - self.draw_double_border_segment(Right, bounds, border, color); + self.draw_double_border_segment(Right, bounds, border, radius, color); } border_style::groove | border_style::ridge => { - self.draw_groove_ridge_border_segment(Right, bounds, border, color, style); + self.draw_groove_ridge_border_segment(Right, bounds, border, radius, color, style); } border_style::inset | border_style::outset => { - self.draw_inset_outset_border_segment(Right, bounds, border, style, color); + self.draw_inset_outset_border_segment(Right, bounds, border, radius, color, style); } } } + // The following comment is wonderful, and stolen from + // gecko:gfx/thebes/gfxContext.cpp:RoundedRectangle for reference. + // + // It does not currently apply to the code, but will be extremely useful in + // the future when the below TODO is addressed. + // + // TODO(cgaebel): Switch from arcs to beziers for drawing the corners. + // Then, add http://www.subcide.com/experiments/fail-whale/ + // to the reftest suite. + // + // --------------------------------------------------------------- + // + // For CW drawing, this looks like: + // + // ...******0** 1 C + // **** + // *** 2 + // ** + // * + // * + // 3 + // * + // * + // + // Where 0, 1, 2, 3 are the control points of the Bezier curve for + // the corner, and C is the actual corner point. + // + // At the start of the loop, the current point is assumed to be + // the point adjacent to the top left corner on the top + // horizontal. Note that corner indices start at the top left and + // continue clockwise, whereas in our loop i = 0 refers to the top + // right corner. + // + // When going CCW, the control points are swapped, and the first + // corner that's drawn is the top left (along with the top segment). + // + // There is considerable latitude in how one chooses the four + // control points for a Bezier curve approximation to an ellipse. + // For the overall path to be continuous and show no corner at the + // endpoints of the arc, points 0 and 3 must be at the ends of the + // straight segments of the rectangle; points 0, 1, and C must be + // collinear; and points 3, 2, and C must also be collinear. This + // leaves only two free parameters: the ratio of the line segments + // 01 and 0C, and the ratio of the line segments 32 and 3C. See + // the following papers for extensive discussion of how to choose + // these ratios: + // + // Dokken, Tor, et al. "Good approximation of circles by + // curvature-continuous Bezier curves." Computer-Aided + // Geometric Design 7(1990) 33--41. + // Goldapp, Michael. "Approximation of circular arcs by cubic + // polynomials." Computer-Aided Geometric Design 8(1991) 227--238. + // Maisonobe, Luc. "Drawing an elliptical arc using polylines, + // quadratic, or cubic Bezier curves." + // http://www.spaceroots.org/documents/ellipse/elliptical-arc.pdf + // + // We follow the approach in section 2 of Goldapp (least-error, + // Hermite-type approximation) and make both ratios equal to + // + // 2 2 + n - sqrt(2n + 28) + // alpha = - * --------------------- + // 3 n - 4 + // + // where n = 3( cbrt(sqrt(2)+1) - cbrt(sqrt(2)-1) ). + // + // This is the result of Goldapp's equation (10b) when the angle + // swept out by the arc is pi/2, and the parameter "a-bar" is the + // expression given immediately below equation (21). + // + // Using this value, the maximum radial error for a circle, as a + // fraction of the radius, is on the order of 0.2 x 10^-3. + // Neither Dokken nor Goldapp discusses error for a general + // ellipse; Maisonobe does, but his choice of control points + // follows different constraints, and Goldapp's expression for + // 'alpha' gives much smaller radial error, even for very flat + // ellipses, than Maisonobe's equivalent. + // + // For the various corners and for each axis, the sign of this + // constant changes, or it might be 0 -- it's multiplied by the + // appropriate multiplier from the list before using. + + #[allow(non_snake_case)] fn draw_border_path(&self, - bounds: Rect, + bounds: &Rect, direction: Direction, border: SideOffsets2D, + radius: &BorderRadii, color: Color) { - let left_top = bounds.origin; - let right_top = left_top + Point2D(bounds.size.width, 0.0); - let left_bottom = left_top + Point2D(0.0, bounds.size.height); - let right_bottom = left_top + Point2D(bounds.size.width, bounds.size.height); + // T = top, B = bottom, L = left, R = right + + let box_TL = bounds.origin; + let box_TR = box_TL + Point2D(bounds.size.width, 0.0); + let box_BL = box_TL + Point2D(0.0, bounds.size.height); + let box_BR = box_TL + Point2D(bounds.size.width, bounds.size.height); + let draw_opts = DrawOptions::new(1.0, 0); let path_builder = self.draw_target.create_path_builder(); - match direction { - Top => { - path_builder.move_to(left_top); - path_builder.line_to(right_top); - path_builder.line_to(right_top + Point2D(-border.right, border.top)); - path_builder.line_to(left_top + Point2D(border.left, border.top)); - } - Left => { - path_builder.move_to(left_top); - path_builder.line_to(left_top + Point2D(border.left, border.top)); - path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom)); - path_builder.line_to(left_bottom); - } - Right => { - path_builder.move_to(right_top); - path_builder.line_to(right_bottom); - path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom)); - path_builder.line_to(right_top + Point2D(-border.right, border.top)); - } - Bottom => { - path_builder.move_to(left_bottom); - path_builder.line_to(left_bottom + Point2D(border.left, -border.bottom)); - path_builder.line_to(right_bottom + Point2D(-border.right, -border.bottom)); - path_builder.line_to(right_bottom); - } - } - let path = path_builder.finish(); - self.draw_target.fill(&path, &ColorPattern::new(color), &draw_opts); - } + let rad_R: AzFloat = 0.; + let rad_BR = rad_R + Float::frac_pi_4(); + let rad_B = rad_BR + Float::frac_pi_4(); + let rad_BL = rad_B + Float::frac_pi_4(); + let rad_L = rad_BL + Float::frac_pi_4(); + let rad_TL = rad_L + Float::frac_pi_4(); + let rad_T = rad_TL + Float::frac_pi_4(); + let rad_TR = rad_T + Float::frac_pi_4(); + + match direction { + Top => { + let edge_TL = box_TL + Point2D(radius.top_left.max(border.left), 0.); + let edge_TR = box_TR + Point2D(-radius.top_right.max(border.right), 0.); + let edge_BR = edge_TR + Point2D(0., border.top); + let edge_BL = edge_TL + Point2D(0., border.top); + + path_builder.move_to(edge_TL); + path_builder.line_to(edge_TR); + + // the origin is the center of the arcs we're about to draw. + let origin = edge_TR + Point2D((border.right - radius.top_right).max(0.), radius.top_right); + // the elbow is the inside of the border's curve. + let distance_to_elbow = (radius.top_right - border.top).max(0.); + + path_builder.arc(origin, radius.top_right, rad_T, rad_TR, false); + path_builder.arc(origin, distance_to_elbow, rad_TR, rad_T, true); + + path_builder.line_to(edge_BR); + path_builder.line_to(edge_BL); + + let origin = edge_TL + Point2D(-(border.left - radius.top_left).max(0.), radius.top_left); + let distance_to_elbow = (radius.top_left - border.top).max(0.); + + path_builder.arc(origin, distance_to_elbow, rad_T, rad_TL, true); + path_builder.arc(origin, radius.top_left, rad_TL, rad_T, false); + } + Left => { + let edge_TL = box_TL + Point2D(0., radius.top_left.max(border.top)); + let edge_BL = box_BL + Point2D(0., -radius.bottom_left.max(border.bottom)); + let edge_TR = edge_TL + Point2D(border.left, 0.); + let edge_BR = edge_BL + Point2D(border.left, 0.); + + path_builder.move_to(edge_BL); + path_builder.line_to(edge_TL); + + let origin = edge_TL + Point2D(radius.top_left, -(border.top - radius.top_left).max(0.)); + let distance_to_elbow = (radius.top_left - border.left).max(0.); + + path_builder.arc(origin, radius.top_left, rad_L, rad_TL, false); + path_builder.arc(origin, distance_to_elbow, rad_TL, rad_L, true); + + path_builder.line_to(edge_TR); + path_builder.line_to(edge_BR); + + let origin = edge_BL + Point2D(radius.bottom_left, (border.bottom - radius.bottom_left).max(0.)); + let distance_to_elbow = (radius.bottom_left - border.left).max(0.); + + path_builder.arc(origin, distance_to_elbow, rad_L, rad_BL, true); + path_builder.arc(origin, radius.bottom_left, rad_BL, rad_L, false); + } + Right => { + let edge_TR = box_TR + Point2D(0., radius.top_right.max(border.top)); + let edge_BR = box_BR + Point2D(0., -radius.bottom_right.max(border.bottom)); + let edge_TL = edge_TR + Point2D(-border.right, 0.); + let edge_BL = edge_BR + Point2D(-border.right, 0.); + + path_builder.move_to(edge_BL); + path_builder.line_to(edge_TL); + + let origin = edge_TR + Point2D(-radius.top_right, -(border.top - radius.top_right).max(0.)); + let distance_to_elbow = (radius.top_right - border.right).max(0.); + + path_builder.arc(origin, distance_to_elbow, rad_R, rad_TR, true); + path_builder.arc(origin, radius.top_right, rad_TR, rad_R, false); + + path_builder.line_to(edge_TR); + path_builder.line_to(edge_BR); + + let origin = edge_BR + Point2D(-radius.bottom_right, (border.bottom - radius.bottom_right).max(0.)); + let distance_to_elbow = (radius.bottom_right - border.right).max(0.); + + path_builder.arc(origin, radius.bottom_right, rad_R, rad_BR, false); + path_builder.arc(origin, distance_to_elbow, rad_BR, rad_R, true); + } + Bottom => { + let edge_BL = box_BL + Point2D(radius.bottom_left.max(border.left), 0.); + let edge_BR = box_BR + Point2D(-radius.bottom_right.max(border.right), 0.); + let edge_TL = edge_BL + Point2D(0., -border.bottom); + let edge_TR = edge_BR + Point2D(0., -border.bottom); + + path_builder.move_to(edge_TL); + path_builder.line_to(edge_TR); + + let origin = edge_BR + Point2D((border.right - radius.bottom_right).max(0.), -radius.bottom_right); + let distance_to_elbow = (radius.bottom_right - border.bottom).max(0.); + + path_builder.arc(origin, distance_to_elbow, rad_B, rad_BR, true); + path_builder.arc(origin, radius.bottom_right, rad_BR, rad_B, false); + + path_builder.line_to(edge_BR); + path_builder.line_to(edge_BL); + + let origin = edge_BL - Point2D((border.left - radius.bottom_left).max(0.), radius.bottom_left); + let distance_to_elbow = (radius.bottom_left - border.bottom).max(0.); + + path_builder.arc(origin, radius.bottom_left, rad_B, rad_BL, false); + path_builder.arc(origin, distance_to_elbow, rad_BL, rad_B, true); + } + } + + let path = path_builder.finish(); + self.draw_target.fill(&path, &ColorPattern::new(color), &draw_opts); + } fn draw_dashed_border_segment(&self, direction: Direction, @@ -312,9 +486,9 @@ impl<'a> RenderContext<'a> { &draw_opts); } - fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, color: Color) { + fn draw_solid_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, radius: &BorderRadii, color: Color) { let rect = bounds.to_azure_rect(); - self.draw_border_path(rect, direction, border, color); + self.draw_border_path(&rect, direction, border, radius, color); } fn get_scaled_bounds(&self, @@ -337,22 +511,23 @@ impl<'a> RenderContext<'a> { return Color::new(color.r * scale_factor, color.g * scale_factor, color.b * scale_factor, color.a); } - fn draw_double_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, color: Color) { + fn draw_double_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, radius: &BorderRadii, color: Color) { let scaled_border = SideOffsets2D::new((1.0/3.0) * border.top, (1.0/3.0) * border.right, (1.0/3.0) * border.bottom, (1.0/3.0) * border.left); let inner_scaled_bounds = self.get_scaled_bounds(bounds, border, 2.0/3.0); // draw the outer portion of the double border. - self.draw_solid_border_segment(direction, bounds, scaled_border, color); + self.draw_solid_border_segment(direction, bounds, scaled_border, radius, color); // draw the inner portion of the double border. - self.draw_border_path(inner_scaled_bounds, direction, scaled_border, color); + self.draw_border_path(&inner_scaled_bounds, direction, scaled_border, radius, color); } fn draw_groove_ridge_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, + radius: &BorderRadii, color: Color, style: border_style::T) { // original bounds as a Rect, with no scaling. @@ -374,17 +549,18 @@ impl<'a> RenderContext<'a> { (Top, false) | (Left, false) | (Right, true) | (Bottom, true) => (color, darker_color) }; // outer portion of the border - self.draw_border_path(original_bounds, direction, scaled_border, outer_color); + self.draw_border_path(&original_bounds, direction, scaled_border, radius, outer_color); // inner portion of the border - self.draw_border_path(inner_scaled_bounds, direction, scaled_border, inner_color); + self.draw_border_path(&inner_scaled_bounds, direction, scaled_border, radius, inner_color); } fn draw_inset_outset_border_segment(&self, direction: Direction, bounds: &Rect, border: SideOffsets2D, - style: border_style::T, - color: Color) { + radius: &BorderRadii, + color: Color, + style: border_style::T) { let is_inset = match style { border_style::inset => true, border_style::outset => false, @@ -398,7 +574,7 @@ impl<'a> RenderContext<'a> { Left => self.scale_color(color, if is_inset { 1.0/6.0 } else { 0.5 }), Right | Bottom => self.scale_color(color, if is_inset { 1.0 } else { 2.0/3.0 }) }; - self.draw_border_path(original_bounds, direction, border, scaled_color); + self.draw_border_path(&original_bounds, direction, border, radius, scaled_color); } pub fn draw_text(&mut self, @@ -502,6 +678,21 @@ impl ToSideOffsetsPx for SideOffsets2D { } } +trait ToRadiiPx { + fn to_radii_px(&self) -> BorderRadii; +} + +impl ToRadiiPx for BorderRadii { + fn to_radii_px(&self) -> BorderRadii { + BorderRadii { + top_left: self.top_left.to_nearest_px() as AzFloat, + top_right: self.top_right.to_nearest_px() as AzFloat, + bottom_left: self.bottom_left.to_nearest_px() as AzFloat, + bottom_right: self.bottom_right.to_nearest_px() as AzFloat, + } + } +} + trait ScaledFontExtensionMethods { fn draw_text_into_context(&self, rctx: &RenderContext, @@ -575,4 +766,3 @@ impl ScaledFontExtensionMethods for ScaledFont { } } } - diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 5f98aae70b7..0fe3f659941 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -27,7 +27,7 @@ use geom::{Point2D, Rect, Size2D, SideOffsets2D}; use gfx::color; use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass, DisplayItem}; use gfx::display_list::{DisplayList, GradientDisplayItem, GradientDisplayItemClass, GradientStop}; -use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem}; +use gfx::display_list::{ImageDisplayItem, ImageDisplayItemClass, LineDisplayItem, BorderRadii}; use gfx::display_list::{LineDisplayItemClass, PseudoDisplayItemClass, SidewaysLeft, SidewaysRight}; use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, StackingContext}; use gfx::display_list::{TextDisplayItem, TextDisplayItemClass, Upright}; @@ -38,11 +38,13 @@ use servo_net::image::holder::ImageHolder; use servo_util::geometry::{mod, Au, ZERO_POINT, ZERO_RECT}; use servo_util::logical_geometry::{LogicalRect, WritingMode}; use servo_util::opts; +use std::default::Default; use style::computed::{AngleAoc, CornerAoc, LP_Length, LP_Percentage, LengthOrPercentage}; use style::computed::{LinearGradient, LinearGradientImage, UrlImage}; use style::computed_values::{background_attachment, background_repeat, border_style, overflow}; use style::computed_values::{visibility}; use style::{ComputedValues, Bottom, Left, RGBA, Right, Top}; +use style::style_structs::Border; use sync::Arc; use url::Url; @@ -149,6 +151,19 @@ pub trait FragmentDisplayListBuilding { -> Rect; } +fn build_border_radius(abs_bounds: &Rect, border_style: &Border) -> BorderRadii { + // TODO(cgaebel): Support border radii even in the case of multiple border widths. + // This is an extennsion of supporting elliptical radii. For now, all percentage + // radii will be relative to the width. + + BorderRadii { + top_left: model::specified(border_style.border_top_left_radius.radius, abs_bounds.size.width), + top_right: model::specified(border_style.border_top_right_radius.radius, abs_bounds.size.width), + bottom_right: model::specified(border_style.border_bottom_right_radius.radius, abs_bounds.size.width), + bottom_left: model::specified(border_style.border_bottom_left_radius.radius, abs_bounds.size.width), + } +} + impl FragmentDisplayListBuilding for Fragment { fn build_display_list_for_background_if_applicable(&self, style: &ComputedValues, @@ -412,7 +427,7 @@ impl FragmentDisplayListBuilding for Fragment { // Append the border to the display list. display_list.push(BorderDisplayItemClass(box BorderDisplayItem { base: BaseDisplayItem::new(*abs_bounds, self.node, *clip_rect), - border: border.to_physical(style.writing_mode), + border_widths: border.to_physical(style.writing_mode), color: SideOffsets2D::new(top_color.to_gfx_color(), right_color.to_gfx_color(), bottom_color.to_gfx_color(), @@ -420,7 +435,8 @@ impl FragmentDisplayListBuilding for Fragment { style: SideOffsets2D::new(style.get_border().border_top_style, style.get_border().border_right_style, style.get_border().border_bottom_style, - style.get_border().border_left_style) + style.get_border().border_left_style), + radius: build_border_radius(abs_bounds, style.get_border()), }), level); } @@ -440,9 +456,10 @@ impl FragmentDisplayListBuilding for Fragment { // Compute the text fragment bounds and draw a border surrounding them. display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem { base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect), - border: SideOffsets2D::new_all_same(Au::from_px(1)), + border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::solid) + style: SideOffsets2D::new_all_same(border_style::solid), + radius: Default::default(), })); // Draw a rectangle representing the baselines. @@ -476,9 +493,10 @@ impl FragmentDisplayListBuilding for Fragment { // This prints a debug border around the border of this fragment. display_list.content.push_back(BorderDisplayItemClass(box BorderDisplayItem { base: BaseDisplayItem::new(absolute_fragment_bounds, self.node, *clip_rect), - border: SideOffsets2D::new_all_same(Au::from_px(1)), + border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)), - style: SideOffsets2D::new_all_same(border_style::solid) + style: SideOffsets2D::new_all_same(border_style::solid), + radius: Default::default(), })); } @@ -956,4 +974,3 @@ impl StackingContextConstruction for DisplayList { } } } - diff --git a/components/style/properties/common_types.rs b/components/style/properties/common_types.rs index 4f68f77bcd0..3e8aad1d73c 100644 --- a/components/style/properties/common_types.rs +++ b/components/style/properties/common_types.rs @@ -20,7 +20,7 @@ pub mod specified { use super::{Au, CSSFloat}; pub use cssparser::Color as CSSColor; - #[deriving(Clone)] + #[deriving(Clone, Show)] pub enum Length { Au_(Au), // application units Em(CSSFloat), @@ -82,11 +82,12 @@ pub mod specified { } } - #[deriving(Clone)] + #[deriving(Clone, Show)] pub enum LengthOrPercentage { LP_Length(Length), LP_Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0] } + impl LengthOrPercentage { fn parse_internal(input: &ComponentValue, negative_ok: bool) -> Result { @@ -502,11 +503,12 @@ pub mod computed { } } - #[deriving(PartialEq, Clone)] + #[deriving(PartialEq, Clone, Show)] pub enum LengthOrPercentage { LP_Length(Au), LP_Percentage(CSSFloat), } + #[allow(non_snake_case)] pub fn compute_LengthOrPercentage(value: specified::LengthOrPercentage, context: &Context) -> LengthOrPercentage { @@ -516,7 +518,7 @@ pub mod computed { } } - #[deriving(PartialEq, Clone)] + #[deriving(PartialEq, Clone, Show)] pub enum LengthOrPercentageOrAuto { LPA_Length(Au), LPA_Percentage(CSSFloat), @@ -532,7 +534,7 @@ pub mod computed { } } - #[deriving(PartialEq, Clone)] + #[deriving(PartialEq, Clone, Show)] pub enum LengthOrPercentageOrNone { LPN_Length(Au), LPN_Percentage(CSSFloat), @@ -602,5 +604,3 @@ pub fn parse_url(input: &str, base_url: &Url) -> Url { UrlParser::new().base_url(base_url).parse(input) .unwrap_or_else(|_| Url::parse("about:invalid").unwrap()) } - - diff --git a/components/style/properties/mod.rs.mako b/components/style/properties/mod.rs.mako index 789dc14f45f..29cc7d3f2fc 100644 --- a/components/style/properties/mod.rs.mako +++ b/components/style/properties/mod.rs.mako @@ -284,6 +284,77 @@ pub mod longhands { % endfor + <%self:longhand name="border-top-left-radius"> + #[deriving(Clone, Show)] + pub struct SpecifiedValue { + pub radius: specified::LengthOrPercentage, + } + + pub mod computed_value { + use super::super::computed; + + #[deriving(Clone, PartialEq, Show)] + pub struct T { + pub radius: computed::LengthOrPercentage, + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T { + radius: computed::LP_Length(Au(0)), + } + } + #[inline] + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + computed_value::T { + radius: computed::compute_LengthOrPercentage(value.radius, context), + } + } + + pub fn parse(input: &[ComponentValue], _: &Url) -> Result { + let mut iter = input.skip_whitespace(); + + let radius = match iter.next() { + None => return Err(()), + Some(cv) => cv, + }; + + let radius = try!(specified::LengthOrPercentage::parse(radius)); + + if iter.next().is_some() { return Err(()); } + + Ok(SpecifiedValue { + radius: radius, + }) + } + + + % for corner in ["top-right", "bottom-right", "bottom-left"]: + <%self:longhand name="border-${corner}-radius"> + pub type SpecifiedValue = super::border_top_left_radius::SpecifiedValue; + + pub mod computed_value { + pub type T = super::super::border_top_left_radius::computed_value::T; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + super::border_top_left_radius::get_initial_value() + } + #[inline] + pub fn to_computed_value(value: SpecifiedValue, context: &computed::Context) + -> computed_value::T { + super::border_top_left_radius::to_computed_value(value, context) + } + + pub fn parse(input: &[ComponentValue], u: &Url) -> Result { + super::border_top_left_radius::parse(input, u) + } + + % endfor + ${new_style_struct("PositionOffsets", is_inherited=False)} % for side in ["top", "right", "bottom", "left"]: @@ -1383,6 +1454,62 @@ pub mod shorthands { }) + <%self:shorthand name="border-radius" sub_properties="${' '.join( + 'border-%s-radius' % (corner) + for corner in ['top-left', 'top-right', 'bottom-right', 'bottom-left'] + )}"> + + use std::iter::Peekable; + + let _ignored = base_url; + + fn parse_one_set_of_border_radii<'a,I>(mut input: Peekable< &'a ComponentValue,I >) + -> Result<[specified::LengthOrPercentage, ..4],()> + where I: Iterator< &'a ComponentValue > { + let (mut count, mut values) = (0u, [specified::LP_Length(specified::Au_(Au(0))), ..4]); + while count < 4 { + let token = match input.peek() { + None => break, + Some(token) => *token, + }; + let value = match specified::LengthOrPercentage::parse(token) { + Err(_) => break, + Ok(value) => value, + }; + drop(input.next()); + values[count] = value; + count += 1 + } + + match count { + 1 => Ok([values[0], values[0], values[0], values[0]]), + 2 => Ok([values[0], values[1], values[0], values[1]]), + 3 => Ok([values[0], values[1], values[2], values[1]]), + 4 => Ok([values[0], values[1], values[2], values[3]]), + _ => Err(()), + } + } + + let input = input.skip_whitespace().peekable(); + let radii = try!(parse_one_set_of_border_radii(input)); + // TODO(pcwalton): Elliptical borders. + + Ok(Longhands { + border_top_left_radius: Some(border_top_left_radius::SpecifiedValue { + radius: radii[0], + }), + border_top_right_radius: Some(border_top_left_radius::SpecifiedValue { + radius: radii[1], + }), + border_bottom_right_radius: Some(border_top_left_radius::SpecifiedValue { + radius: radii[2], + }), + border_bottom_left_radius: Some(border_top_left_radius::SpecifiedValue { + radius: radii[3], + }), + }) + + <%self:shorthand name="font" sub_properties="font-style font-variant font-weight font-size line-height font-family"> let mut iter = input.skip_whitespace();