Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] svg: support <circle> element #17681

Closed
wants to merge 10 commits into from

Some generated files are not rendered by default. Learn more.

@@ -14,7 +14,9 @@ opt-level = 3
# debug = true
# lto = false

[patch.crates-io]
[replace]
"https://github.com/servo/webrender#webrender_api:0.50.0" = { path = "/home/stshine/Programs/webrender/webrender_api" }
"https://github.com/servo/webrender#webrender:0.50.0" = { path = "/home/stshine/Programs/webrender/webrender" }
# If you need to temporarily test Servo with a local fork of some upstream
# crate, add that here. Use the form:
#
@@ -600,6 +600,7 @@ pub enum DisplayItem {
SolidColor(Box<SolidColorDisplayItem>),
Text(Box<TextDisplayItem>),
Image(Box<ImageDisplayItem>),
Svg(Box<SvgDisplayItem>),
Border(Box<BorderDisplayItem>),
Gradient(Box<GradientDisplayItem>),
RadialGradient(Box<RadialGradientDisplayItem>),
@@ -906,6 +907,13 @@ pub enum TextOrientation {
SidewaysRight,
}

/// Paints an svg.
#[derive(Clone, HeapSizeOf, Deserialize, Serialize)]
pub struct SvgDisplayItem {
pub base: BaseDisplayItem,
pub key: webrender_api::GeometryKey,
}

/// Paints an image.
#[derive(Clone, Deserialize, HeapSizeOf, Serialize)]
pub struct ImageDisplayItem {
@@ -1242,6 +1250,7 @@ impl DisplayItem {
DisplayItem::SolidColor(ref solid_color) => &solid_color.base,
DisplayItem::Text(ref text) => &text.base,
DisplayItem::Image(ref image_item) => &image_item.base,
DisplayItem::Svg(ref svg_item) => &svg_item.base,
DisplayItem::Border(ref border) => &border.base,
DisplayItem::Gradient(ref gradient) => &gradient.base,
DisplayItem::RadialGradient(ref gradient) => &gradient.base,
@@ -1367,6 +1376,7 @@ impl fmt::Debug for DisplayItem {
text.range.begin().0 as usize..(text.range.begin().0 + text.range.length().0) as usize])
}
DisplayItem::Image(_) => "Image".to_owned(),
DisplayItem::Svg(_) => "SVG".to_owned(),
DisplayItem::Border(_) => "Border".to_owned(),
DisplayItem::Gradient(_) => "Gradient".to_owned(),
DisplayItem::RadialGradient(_) => "RadialGradient".to_owned(),
@@ -57,6 +57,7 @@ use style::properties::longhands::list_style_image;
use style::selector_parser::{PseudoElement, RestyleDamage};
use style::servo::restyle_damage::{BUBBLE_ISIZES, RECONSTRUCT_FLOW};
use style::values::Either;
use fragment::{SVGData, SVGItem};
use table::TableFlow;
use table_caption::TableCaptionFlow;
use table_cell::TableCellFlow;
@@ -83,6 +84,13 @@ pub enum ConstructionResult {
/// This node contributed some object or objects that will be needed to construct a proper flow
/// later up the tree, but these objects have not yet found their home.
ConstructionItem(ConstructionItem),

/// This node contributed an SVG item and all its children. So the general
/// idea is, SVG items do not need layout except percentage size resolving
/// so we can flatten them during construction. This results a O(n^2) copy,
/// but since SVG is mostly flat this may be doable.
// FIXME(stshine): figure out if this is sufficient & efficient.
SVGItem(Vec<SVGItem>)
}

impl ConstructionResult {
@@ -96,6 +104,7 @@ impl ConstructionResult {
match *self {
ConstructionResult::None => 0,
ConstructionResult::ConstructionItem(_) => 0,
ConstructionResult::SVGItem(_) => 0,
ConstructionResult::Flow(ref flow_ref, _) => flow::base(&**flow_ref).debug_id(),
}
}
@@ -377,10 +386,6 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
let data = node.canvas_data().unwrap();
SpecificFragmentInfo::Canvas(box CanvasFragmentInfo::new(data))
}
Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => {
let data = node.svg_data().unwrap();
SpecificFragmentInfo::Svg(box SvgFragmentInfo::new(data))
}
_ => {
// This includes pseudo-elements.
SpecificFragmentInfo::Generic
@@ -567,6 +572,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
// TODO: Implement anonymous table objects for missing parents
// CSS 2.1 § 17.2.1, step 3-2
}
ConstructionResult::SVGItem(_) => {}
}
}

@@ -775,6 +781,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
}
match kid.get_construction_result() {
ConstructionResult::None => {}
ConstructionResult::SVGItem(_) => {}
ConstructionResult::Flow(flow, kid_abs_descendants) => {
if !flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED) {
opt_inline_block_splits.push_back(InlineBlockSplit::new(
@@ -1029,7 +1036,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
table_wrapper_flow.add_new_child(kid_flow);
}
}
ConstructionResult::None | ConstructionResult::ConstructionItem(_) => {}
ConstructionResult::None | ConstructionResult::ConstructionItem(_) | ConstructionResult::SVGItem(_) => {}
}
}
}
@@ -1355,6 +1362,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>

match *node.construction_result_mut(&mut *data) {
ConstructionResult::None => true,
ConstructionResult::SVGItem(_) => false,
ConstructionResult::Flow(ref mut flow, _) => {
// The node's flow is of the same type and has the same set of children and can
// therefore be repaired by simply propagating damage and style to the flow.
@@ -1457,6 +1465,48 @@ impl<'a, ConcreteThreadSafeLayoutNode> PostorderNodeMutTraversal<ConcreteThreadS
return;
}

match node.type_id() {
Some(LayoutNodeType::Element(LayoutElementType::SVGCircleElement)) => {
let item = SVGItem {
data: SVGData::Circle,
style: node.style(self.style_context())
};
self.set_flow_construction_result(node, ConstructionResult::SVGItem(vec![item]));
return
}

Some(LayoutNodeType::Element(LayoutElementType::SVGSVGElement)) => {
// FIXME(stshine): Handle <svg> inside svg.
if node.svg_data().is_none() {
return
}
let data = node.svg_data().unwrap();
let mut info = SvgFragmentInfo::new(data);
for kid in node.children() {
match kid.get_construction_result() {
ConstructionResult::SVGItem(item) => {
info.items.append(&mut item.clone());
}
_ => {}
}
}
let specific_fragment_info = SpecificFragmentInfo::Svg(box info);
let fragment = Fragment::new(node, specific_fragment_info, self.layout_context);
let mut fragments = IntermediateInlineFragments::new();
fragments.fragments.push_back(fragment);
let construction_item = ConstructionItem::InlineFragments(
InlineFragmentsConstructionResult {
splits: LinkedList::new(),
fragments: fragments
});
self.set_flow_construction_result(
node, ConstructionResult::ConstructionItem(construction_item)
);
return
}
_ => {}
}

// Get the `display` property for this node, and determine whether this node is floated.
let (display, float, positioning) = match node.type_id() {
None => {
@@ -20,7 +20,7 @@ use flex::FlexFlow;
use flow::{BaseFlow, Flow, IS_ABSOLUTELY_POSITIONED};
use flow_ref::FlowRef;
use fragment::{CanvasFragmentSource, CoordinateSystem, Fragment, ImageFragmentInfo, ScannedTextFragmentInfo};
use fragment::{SpecificFragmentInfo, TruncatedFragmentInfo};
use fragment::{SpecificFragmentInfo, SVGData, SVGItem, TruncatedFragmentInfo};
use gfx::display_list;
use gfx::display_list::{BLUR_INFLATION_FACTOR, BaseDisplayItem, BorderDetails, BorderDisplayItem};
use gfx::display_list::{BorderRadii, BoxShadowClipMode, BoxShadowDisplayItem, ClippingRegion};
@@ -29,7 +29,7 @@ use gfx::display_list::{GradientDisplayItem, IframeDisplayItem, ImageBorder, Ima
use gfx::display_list::{LineDisplayItem, NormalBorder, OpaqueNode, PushTextShadowDisplayItem};
use gfx::display_list::{PopTextShadowDisplayItem, RadialGradientDisplayItem, ScrollRoot};
use gfx::display_list::{ScrollRootType, SolidColorDisplayItem, StackingContext, StackingContextType};
use gfx::display_list::{TextDisplayItem, TextOrientation, WebRenderImageInfo};
use gfx::display_list::{SvgDisplayItem, TextDisplayItem, TextOrientation, WebRenderImageInfo};
use gfx_traits::{combine_id_with_fragment_type, FragmentType, StackingContextId};
use inline::{FIRST_FRAGMENT_OF_ELEMENT, InlineFlow, LAST_FRAGMENT_OF_ELEMENT};
use ipc_channel::ipc;
@@ -46,6 +46,7 @@ use servo_url::ServoUrl;
use std::{cmp, f32};
use std::collections::HashMap;
use std::default::Default;
use std::f32::consts::PI;
use std::mem;
use std::sync::Arc;
use style::computed_values::{background_attachment, background_clip, background_origin};
@@ -60,7 +61,9 @@ use style::values::{Either, RGBA};
use style::values::computed::{Angle, Gradient, GradientItem, LengthOrPercentage, Percentage};
use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
use style::values::computed::effects::SimpleShadow;
// use style::values::computed::SVGPaint;
use style::values::computed::image::{EndingShape, LineDirection};
use style::values::generics::svg::SVGPaintKind;
use style::values::generics::background::BackgroundSize;
use style::values::generics::effects::Filter;
use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
@@ -72,10 +75,11 @@ use style_traits::CSSPixel;
use style_traits::ToCss;
use style_traits::cursor::Cursor;
use table_cell::CollapsedBordersForCell;
use webrender_api::{ClipAndScrollInfo, ClipId, ColorF, ComplexClipRegion, GradientStop, LineStyle};
use webrender_api::{ClipAndScrollInfo, ClipId, ColorF, ColorU, ComplexClipRegion, GradientStop, LineStyle};
use webrender_api::{LocalClip, RepeatMode, ScrollPolicy, ScrollSensitivity, StickyFrameInfo};
use webrender_api::StickySideConstraint;
use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};
use webrender_api::{Command, Geometry, GeometryItem, Shape};

trait ResolvePercentage {
fn resolve(&self, length: u32) -> u32;
@@ -110,6 +114,54 @@ fn establishes_containing_block_for_absolute(can_establish_containing_block: Est
position::T::static_ != positioning
}

fn to_geometry(items: &[SVGItem], width: Au, height: Au) -> Geometry {
let mut geometry = Vec::new();
for item in items {
match item.data {
SVGData::Circle => {
let fill_style = match item.style.get_inheritedsvg().fill.kind {
SVGPaintKind::Color(color) =>

This comment has been minimized.

Copy link
@emilio

emilio Jul 12, 2017

Member

nit: Indentation here looks weird, without a wrapping block.

ColorU {
r: color.red,
g: color.green,
b: color.blue,
a: color.alpha,
},
_ =>
ColorU {
r: 0,
g: 0,
b: 0,
a: 255,
}

};
let cx = item.style.get_svg().cx.to_used_value(width).to_f32_px();
let cy = item.style.get_svg().cy.to_used_value(height).to_f32_px();
let diagonal = Au::from_f32_px(((width.0 * width.0 + height.0 * height.0) as f32).sqrt());
let radius = item.style.get_svg().r.to_used_value(diagonal).to_f32_px();
let center = Point2D::new(cx, cy);
// Mathematically, a ‘circle’ element is mapped to an equivalent
// ‘path’ element that consists of four elliptical arc segments,
// each covering a quarter of the circle. The path begins at the
// "3 o'clock" point on the radius and proceeds in a clock-wise
// direction (before any transformations).
let mut path = Vec::with_capacity(4);
path.push(Command::Arc(center, radius, PI / 2.0, PI));
path.push(Command::Arc(center, radius, PI, PI * 1.5));
path.push(Command::Arc(center, radius, PI * 1.5, PI * 2.0));
path.push(Command::Arc(center, radius, 0.0, PI / 2.0));
geometry.push(GeometryItem::Shape(Shape {
path: path,
fill: fill_style
}));
}
_ => {}
}
}
geometry
}

trait RgbColor {
fn rgb(r: u8, g: u8, b: u8) -> Self;
}
@@ -1940,8 +1992,7 @@ impl FragmentDisplayListBuilding for Fragment {
SpecificFragmentInfo::InlineBlock(_) |
SpecificFragmentInfo::InlineAbsoluteHypothetical(_) |
SpecificFragmentInfo::InlineAbsolute(_) |
SpecificFragmentInfo::TruncatedFragment(_) |
SpecificFragmentInfo::Svg(_) => {
SpecificFragmentInfo::TruncatedFragment(_) => {
if opts::get().show_debug_fragment_borders {
self.build_debug_borders_around_fragment(state,
stacking_relative_border_box,
@@ -1988,6 +2039,26 @@ impl FragmentDisplayListBuilding for Fragment {
}));
}
}
SpecificFragmentInfo::Svg(ref mut info) => {
let base = state.create_base_display_item(
&stacking_relative_content_box,
LocalClip::from(clip.to_rectf()),
self.node,
self.style.get_cursor(Cursor::Default),
DisplayListSection::Content);
let geometry = to_geometry(&info.items,
stacking_relative_content_box.size.width,
stacking_relative_content_box.size.height);
state
.layout_context
.image_cache
.update_geometry(info.geometry_key, geometry);

state.add_display_item(DisplayItem::Svg(box SvgDisplayItem {
base: base,
key: info.geometry_key,
}));
}
SpecificFragmentInfo::Canvas(ref canvas_fragment_info) => {
let computed_width = canvas_fragment_info.dom_width.to_px();
let computed_height = canvas_fragment_info.dom_height.to_px();
@@ -31,7 +31,7 @@ use net_traits::image::base::{Image, ImageMetadata};
use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder};
use range::*;
use script_layout_interface::{HTMLCanvasData, HTMLCanvasDataSource};
use script_layout_interface::SVGSVGData;
use script_layout_interface::SVGImageData;
use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
use serde::ser::{Serialize, SerializeStruct, Serializer};
use servo_url::ServoUrl;
@@ -360,18 +360,21 @@ impl CanvasFragmentInfo {
pub struct SvgFragmentInfo {
pub dom_width: Au,
pub dom_height: Au,
pub geometry_key: webrender_api::GeometryKey,
pub items: Vec<SVGItem>,
}

impl SvgFragmentInfo {
pub fn new(data: SVGSVGData) -> SvgFragmentInfo {
pub fn new(data: SVGImageData) -> SvgFragmentInfo {
SvgFragmentInfo {
dom_width: Au::from_px(data.width as i32),
dom_height: Au::from_px(data.height as i32),
geometry_key: data.geometry_key,
items: vec![],
}
}
}


/// A fragment that represents a replaced content image and its accompanying borders, shadows, etc.
#[derive(Clone)]
pub struct ImageFragmentInfo {
@@ -3157,3 +3160,17 @@ impl Serialize for DebugId {
serializer.serialize_u16(self.0)
}
}


#[derive(Debug, Clone)]
pub enum SVGData {
SVG,
Circle
}

/// Represent an SVG element in layout.
#[derive(Debug, Clone)]
pub struct SVGItem {
pub data: SVGData,
pub style: ServoArc<ComputedValues>
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.