Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
use std::collections::HashMap;
use webrender::scene::Scene;
use webrender_api::{units::LayoutRect, BorderDetails, BorderStyle, BuiltDisplayList};
use webrender_api::{ColorF, DisplayItem, PipelineId, PropertyBinding, SpatialId};
use webrender_api::{ClipId, SpatialTreeItem, ClipChainId};
fn color_to_string(
color: ColorF,
) -> String {
match (color.r, color.g, color.b, color.a) {
(1.0, 0.0, 0.0, 1.0) => "red".into(),
_ => {
format!("{} {} {} {}",
color.r * 255.0,
color.g * 255.0,
color.b * 255.0,
color.a,
)
}
}
}
fn color_to_string_array(
color: ColorF,
) -> String {
match (color.r, color.g, color.b, color.a) {
(1.0, 0.0, 0.0, 1.0) => "red".into(),
_ => {
format!("{}, {}, {}, {}",
color.r * 255.0,
color.g * 255.0,
color.b * 255.0,
color.a,
)
}
}
}
fn style_to_string(
style: BorderStyle,
) -> String {
match style {
BorderStyle::None => "none",
BorderStyle::Solid => "solid",
BorderStyle::Double => "double",
BorderStyle::Dotted => "dotted",
BorderStyle::Dashed => "dashed",
BorderStyle::Hidden => "hidden",
BorderStyle::Ridge => "ridge",
BorderStyle::Inset => "inset",
BorderStyle::Outset => "outset",
BorderStyle::Groove => "groove",
}.into()
}
#[derive(Debug)]
enum SpatialNodeKind {
Reference {
},
Scroll {
},
Sticky {
},
}
#[derive(Debug)]
struct SpatialNode {
wrench_id: u64,
}
struct YamlWriter {
out: String,
indent: String,
spatial_nodes: HashMap<SpatialId, SpatialNode>,
clip_id_map: HashMap<ClipId, u64>,
clipchain_id_map: HashMap<ClipChainId, u64>,
next_wrench_id: u64,
}
impl YamlWriter {
fn new() -> Self {
YamlWriter {
out: String::new(),
indent: String::new(),
spatial_nodes: HashMap::new(),
next_wrench_id: 2,
clip_id_map: HashMap::new(),
clipchain_id_map: HashMap::new(),
}
}
fn push_level(&mut self) {
self.indent.push_str(" ");
}
fn pop_level(&mut self) {
self.indent.truncate(self.indent.len() - 2);
}
fn add_clip_id(
&mut self,
clip_id: ClipId
) -> u64 {
let id = self.next_wrench_id;
self.next_wrench_id += 1;
let _prev = self.clip_id_map.insert(clip_id, id);
assert!(_prev.is_none());
id
}
fn add_clipchain_id(
&mut self,
clipchain_id: ClipChainId
) -> u64 {
let id = self.next_wrench_id;
self.next_wrench_id += 1;
let _prev = self.clipchain_id_map.insert(clipchain_id, id);
assert!(_prev.is_none());
id
}
fn add_and_write_spatial_node(
&mut self,
spatial_id: SpatialId,
parent: SpatialId,
kind: SpatialNodeKind,
) {
match kind {
SpatialNodeKind::Reference {} => {
self.write_line("- type: reference-frame");
self.push_level();
self.write_line(&format!("id: {}", self.next_wrench_id));
if let Some(parent) = self.spatial_nodes.get(&parent) {
self.write_line(&format!("spatial-id: {}", parent.wrench_id));
}
self.pop_level();
}
SpatialNodeKind::Scroll {} => {
let parent_id = self.spatial_nodes[&parent].wrench_id;
self.write_line("- type: scroll-frame");
self.push_level();
self.write_line(&format!("id: {}", self.next_wrench_id));
self.write_bounds(LayoutRect::zero());
self.write_line(&format!("spatial-id: {}", parent_id));
self.pop_level();
}
SpatialNodeKind::Sticky {} => {
let parent_id = self.spatial_nodes[&parent].wrench_id;
self.write_line("- type: sticky-frame");
self.push_level();
self.write_line(&format!("id: {}", self.next_wrench_id));
self.write_line(&format!("spatial-id: {}", parent_id));
self.write_bounds(LayoutRect::zero());
self.pop_level();
}
}
let _prev = self.spatial_nodes.insert(
spatial_id,
SpatialNode {
wrench_id: self.next_wrench_id,
},
);
assert!(_prev.is_none());
self.next_wrench_id += 1;
}
fn write_color(
&mut self,
color: ColorF,
) {
self.write_line(
&format!("color: {}", color_to_string(color))
);
}
fn write_rect(
&mut self,
tag: &str,
rect: LayoutRect,
) {
self.write_line(
&format!("{}: {} {} {} {}",
tag,
rect.min.x,
rect.min.y,
rect.width(),
rect.height(),
)
);
}
fn write_bounds(
&mut self,
bounds: LayoutRect,
) {
self.write_rect("bounds", bounds);
}
fn maybe_write_clip_rect(
&mut self,
bounds: LayoutRect,
clip_rect: LayoutRect,
) {
if bounds != clip_rect {
self.write_rect("clip-rect", clip_rect);
}
}
fn create_savepoint(
&mut self,
) -> (usize, usize) {
(self.out.len(), self.indent.len())
}
fn restore_savepoint(
&mut self,
p: (usize, usize),
) {
self.out.truncate(p.0);
self.indent.truncate(p.1);
}
fn write_line(
&mut self,
s: &str,
) {
self.out.push_str(&self.indent);
self.out.push_str(s);
self.out.push_str("\n");
}
fn write_spatial_id(
&mut self,
id: SpatialId,
) {
let spatial_node = self.spatial_nodes
.get(&id)
.expect(&format!("unknown spatial node {:?}", id));
self.write_line(&format!("spatial-id: {}", spatial_node.wrench_id));
}
fn write_clip_chain_id(
&mut self,
id: ClipChainId,
) {
if id != ClipChainId::INVALID {
let clip_chain_id = self.clipchain_id_map[&id];
self.write_line(&format!("clip-chain: {}", clip_chain_id));
}
}
fn build_spatial_tree(
&mut self,
dl: &BuiltDisplayList,
pipeline_id: PipelineId,
) {
// Insert root ref + scroll frames
self.add_and_write_spatial_node(
SpatialId::root_reference_frame(pipeline_id),
SpatialId::root_reference_frame(pipeline_id),
SpatialNodeKind::Reference { },
);
self.add_and_write_spatial_node(
SpatialId::root_scroll_node(pipeline_id),
SpatialId::root_reference_frame(pipeline_id),
SpatialNodeKind::Scroll { },
);
dl.iter_spatial_tree(|item| {
match item {
SpatialTreeItem::ScrollFrame(descriptor) => {
self.add_and_write_spatial_node(
descriptor.scroll_frame_id,
descriptor.parent_space,
SpatialNodeKind::Scroll {
},
);
}
SpatialTreeItem::ReferenceFrame(descriptor) => {
self.add_and_write_spatial_node(
descriptor.reference_frame.id,
descriptor.parent_spatial_id,
SpatialNodeKind::Reference {
},
);
}
SpatialTreeItem::StickyFrame(descriptor) => {
self.add_and_write_spatial_node(
descriptor.id,
descriptor.parent_spatial_id,
SpatialNodeKind::Sticky {
},
);
}
SpatialTreeItem::Invalid => {
unreachable!();
}
}
});
}
fn write_pipeline(
&mut self,
scene: &Scene,
pipeline_id: PipelineId,
) -> Result<(), String> {
enum ContextKind {
Root,
StackingContext {
// sc_info: StackingContextInfo,
},
}
struct BuildContext {
kind: ContextKind,
}
let pipeline = &scene.pipelines[&pipeline_id];
self.build_spatial_tree(
&pipeline.display_list.display_list,
pipeline_id,
);
let mut stack = vec![BuildContext {
kind: ContextKind::Root,
}];
let mut traversal = pipeline.display_list.iter();
'outer: while let Some(bc) = stack.pop() {
loop {
let item = match traversal.next() {
Some(item) => item,
None => break,
};
match item.item() {
DisplayItem::PushStackingContext(info) => {
self.write_line("- type: stacking-context");
self.push_level();
self.write_spatial_id(info.spatial_id);
if let Some(clip_chain_id) = info.stacking_context.clip_chain_id {
self.write_clip_chain_id(clip_chain_id);
}
self.write_line(
&format!("bounds: {} {} 0 0",
0.0, //info.origin.x + info.ref_frame_offset.x,
0.0, //info.origin.y + info.ref_frame_offset.y,
)
);
self.write_line("items:");
self.push_level();
let new_context = BuildContext {
kind: ContextKind::StackingContext {
// sc_info,
},
};
stack.push(bc);
stack.push(new_context);
traversal = item.sub_iter();
continue 'outer;
}
DisplayItem::PopStackingContext => {
self.pop_level();
self.pop_level();
}
DisplayItem::Iframe(info) => {
self.write_line("- type: iframe");
self.push_level();
self.write_spatial_id(info.space_and_clip.spatial_id);
self.write_clip_chain_id(info.space_and_clip.clip_chain_id);
self.write_bounds(info.bounds);
self.maybe_write_clip_rect(info.bounds, info.clip_rect);
self.write_line(&format!("id: [{}, {}]",
info.pipeline_id.0,
info.pipeline_id.1,
));
self.pop_level();
}
DisplayItem::Rectangle(info) => {
self.write_line("- type: rect");
self.push_level();
self.write_spatial_id(info.common.spatial_id);
self.write_clip_chain_id(info.common.clip_chain_id);
self.write_bounds(info.bounds);
self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
let color = match info.color {
PropertyBinding::Binding(..) => {
println!("WARN: Property color bindings are unsupported");
ColorF::new(1.0, 0.0, 1.0, 0.5)
}
PropertyBinding::Value(color) => {
color
}
};
if color.a > 0.0 {
self.write_color(color);
}
self.pop_level();
}
DisplayItem::Text(info) => {
self.write_line("- type: rect");
self.push_level();
self.write_spatial_id(info.common.spatial_id);
self.write_clip_chain_id(info.common.clip_chain_id);
self.write_bounds(info.bounds);
self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
self.write_color(ColorF::new(1.0, 0.0, 0.0, 0.5));
self.pop_level();
}
DisplayItem::Border(info) => {
let sp = self.create_savepoint();
self.write_line("- type: border");
self.push_level();
self.write_spatial_id(info.common.spatial_id);
self.write_clip_chain_id(info.common.clip_chain_id);
self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
self.write_bounds(info.bounds);
match info.details {
BorderDetails::Normal(border) => {
self.write_line("border-type: normal");
let colors = [
border.top.color,
border.right.color,
border.bottom.color,
border.left.color,
];
if colors.iter().all(|c| c.a == 0.0) {
self.restore_savepoint(sp);
continue;
}
if colors.iter().all(|c| *c == border.top.color) {
self.write_color(border.top.color);
} else {
self.write_line(&format!(
"color: [ [{}], [{}], [{}], [{}] ]",
color_to_string_array(colors[0]),
color_to_string_array(colors[1]),
color_to_string_array(colors[2]),
color_to_string_array(colors[3]),
)
);
}
let styles = [
border.top.style,
border.right.style,
border.bottom.style,
border.left.style,
];
if styles.iter().all(|s| *s == border.top.style) {
self.write_line(&format!(
"style: {}", style_to_string(styles[0]),
)
);
} else {
self.write_line(&format!(
"style: [ {}, {}, {}, {} ]",
style_to_string(styles[0]),
style_to_string(styles[1]),
style_to_string(styles[2]),
style_to_string(styles[3]),
)
);
}
self.write_line("width: [1, 1, 1, 1]");
if !border.radius.is_zero() {
self.write_line("radius: {");
self.push_level();
self.write_line(
&format!("top-left: [{}, {}],",
border.radius.top_left.width,
border.radius.top_left.height,
)
);
self.write_line(
&format!("top-right: [{}, {}],",
border.radius.top_right.width,
border.radius.top_right.height,
)
);
self.write_line(
&format!("bottom-left: [{}, {}],",
border.radius.bottom_left.width,
border.radius.bottom_left.height,
)
);
self.write_line(
&format!("bottom-right: [{}, {}],",
border.radius.bottom_right.width,
border.radius.bottom_right.height,
)
);
self.pop_level();
self.write_line("}");
}
}
BorderDetails::NinePatch(..) => {
todo!();
}
}
self.pop_level();
}
DisplayItem::Image(info) => {
self.write_line("- type: image");
self.push_level();
self.write_spatial_id(info.common.spatial_id);
self.write_clip_chain_id(info.common.clip_chain_id);
self.write_bounds(info.bounds);
self.maybe_write_clip_rect(info.bounds, info.common.clip_rect);
self.write_line(
&format!("src: checkerboard(2,8,8,{},{})",
((info.bounds.width() - 2.0) / 8.0).ceil() as i32,
((info.bounds.height() - 2.0) / 8.0).ceil() as i32,
),
);
self.pop_level();
}
DisplayItem::RectClip(info) => {
let clip_id = self.add_clip_id(info.id);
self.write_line("- type: clip");
self.push_level();
self.write_line(&format!("id: {}", clip_id));
self.write_spatial_id(info.spatial_id);
self.write_rect("bounds", info.clip_rect);
self.pop_level();
}
DisplayItem::ImageMaskClip(info) => {
let clip_id = self.add_clip_id(info.id);
self.write_line("- type: clip");
self.push_level();
self.write_line(&format!("id: {}", clip_id));
self.write_spatial_id(info.spatial_id);
self.write_rect("bounds", info.image_mask.rect);
self.pop_level();
}
DisplayItem::RoundedRectClip(info) => {
let clip_id = self.add_clip_id(info.id);
self.write_line("- type: clip");
self.push_level();
self.write_line(&format!("id: {}", clip_id));
self.write_spatial_id(info.spatial_id);
self.write_line("complex:");
self.push_level();
self.write_rect("- rect", info.clip.rect);
self.push_level();
self.write_line("radius: {");
self.push_level();
self.write_line(
&format!("top-left: [{}, {}],",
info.clip.radii.top_left.width,
info.clip.radii.top_left.height,
));
self.write_line(
&format!("top-right: [{}, {}],",
info.clip.radii.top_right.width,
info.clip.radii.top_right.height,
));
self.write_line(
&format!("bottom-right: [{}, {}],",
info.clip.radii.bottom_right.width,
info.clip.radii.bottom_right.height,
));
self.write_line(
&format!("bottom-left: [{}, {}],",
info.clip.radii.bottom_left.width,
info.clip.radii.bottom_left.height,
));
self.pop_level();
self.write_line("}");
self.pop_level();
self.pop_level();
self.pop_level();
}
DisplayItem::ClipChain(info) => {
let clipchain_id = self.add_clipchain_id(info.id);
self.write_line("- type: clip-chain");
self.push_level();
self.write_line(&format!("id: {}", clipchain_id));
let mut clips = String::new();
clips.push_str("clips: [");
for id in item.clip_chain_items().iter() {
clips.push_str(&format!("{}, ", self.clip_id_map[&id]))
}
clips.push_str("]");
self.write_line(&clips);
self.pop_level();
}
// TODO(gw): Ignored elements - we should as support for
// these as needed.
DisplayItem::SetGradientStops => {}
DisplayItem::SetFilterOps => {}
DisplayItem::SetFilterData => {}
DisplayItem::SetFilterPrimitives => {}
DisplayItem::SetPoints => {}
DisplayItem::PopAllShadows => {}
DisplayItem::ReuseItems(..) => {}
DisplayItem::RetainedItems(..) => {}
DisplayItem::RepeatingImage(..) => {}
DisplayItem::YuvImage(..) => {}
DisplayItem::BackdropFilter(..) => {}
DisplayItem::PushShadow(..) => {}
DisplayItem::Gradient(..) => {}
DisplayItem::RadialGradient(..) => {}
DisplayItem::ConicGradient(..) => {}
DisplayItem::ClearRectangle(..) => {}
DisplayItem::Line(..) => {}
DisplayItem::HitTest(..) => {}
DisplayItem::PushReferenceFrame(..) => {}
DisplayItem::PopReferenceFrame => {}
DisplayItem::DebugMarker(..) => {}
DisplayItem::BoxShadow(..) => {}
};
}
match bc.kind {
ContextKind::Root => {}
ContextKind::StackingContext { } => {
// self.pop_stacking_context(sc_info);
}
}
}
assert!(stack.is_empty());
Ok(())
}
fn write_scene(
mut self,
scene: &Scene
) -> Result<String, String> {
self.write_line(&format!("# process-capture"));
self.write_line("---");
self.write_line("root:");
self.push_level();
self.write_line("items:");
self.push_level();
if let Some(root_pipeline_id) = scene.root_pipeline_id {
self.write_pipeline(scene, root_pipeline_id)?;
}
self.pop_level();
self.pop_level();
assert!(self.indent.is_empty());
if scene.pipelines.len() > 1 {
self.write_line("pipelines:");
self.push_level();
for (id, _) in &scene.pipelines {
if Some(*id) == scene.root_pipeline_id {
continue;
}
self.write_line(&format!("- id: [{}, {}]", id.0, id.1));
self.push_level();
self.write_line("items:");
self.push_level();
self.write_pipeline(scene, *id)?;
self.pop_level();
self.pop_level();
}
self.pop_level();
}
Ok(self.out)
}
}
pub fn scene_to_yaml(
scene: &Scene,
) -> Result<String, String> {
let writer = YamlWriter::new();
writer.write_scene(scene)
}