wrench.rs (25900B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 use std::collections::HashMap; 6 7 use webrender::scene::Scene; 8 use webrender_api::{units::LayoutRect, BorderDetails, BorderStyle, BuiltDisplayList}; 9 use webrender_api::{ColorF, DisplayItem, PipelineId, PropertyBinding, SpatialId}; 10 use webrender_api::{ClipId, SpatialTreeItem, ClipChainId}; 11 12 fn color_to_string( 13 color: ColorF, 14 ) -> String { 15 match (color.r, color.g, color.b, color.a) { 16 (1.0, 0.0, 0.0, 1.0) => "red".into(), 17 _ => { 18 format!("{} {} {} {}", 19 color.r * 255.0, 20 color.g * 255.0, 21 color.b * 255.0, 22 color.a, 23 ) 24 } 25 } 26 } 27 28 fn color_to_string_array( 29 color: ColorF, 30 ) -> String { 31 match (color.r, color.g, color.b, color.a) { 32 (1.0, 0.0, 0.0, 1.0) => "red".into(), 33 _ => { 34 format!("{}, {}, {}, {}", 35 color.r * 255.0, 36 color.g * 255.0, 37 color.b * 255.0, 38 color.a, 39 ) 40 } 41 } 42 } 43 44 fn style_to_string( 45 style: BorderStyle, 46 ) -> String { 47 match style { 48 BorderStyle::None => "none", 49 BorderStyle::Solid => "solid", 50 BorderStyle::Double => "double", 51 BorderStyle::Dotted => "dotted", 52 BorderStyle::Dashed => "dashed", 53 BorderStyle::Hidden => "hidden", 54 BorderStyle::Ridge => "ridge", 55 BorderStyle::Inset => "inset", 56 BorderStyle::Outset => "outset", 57 BorderStyle::Groove => "groove", 58 }.into() 59 } 60 61 #[derive(Debug)] 62 enum SpatialNodeKind { 63 Reference { 64 }, 65 Scroll { 66 }, 67 Sticky { 68 }, 69 } 70 71 #[derive(Debug)] 72 struct SpatialNode { 73 wrench_id: u64, 74 } 75 76 struct YamlWriter { 77 out: String, 78 indent: String, 79 spatial_nodes: HashMap<SpatialId, SpatialNode>, 80 clip_id_map: HashMap<ClipId, u64>, 81 clipchain_id_map: HashMap<ClipChainId, u64>, 82 next_wrench_id: u64, 83 } 84 85 impl YamlWriter { 86 fn new() -> Self { 87 YamlWriter { 88 out: String::new(), 89 indent: String::new(), 90 spatial_nodes: HashMap::new(), 91 next_wrench_id: 2, 92 clip_id_map: HashMap::new(), 93 clipchain_id_map: HashMap::new(), 94 } 95 } 96 97 fn push_level(&mut self) { 98 self.indent.push_str(" "); 99 } 100 101 fn pop_level(&mut self) { 102 self.indent.truncate(self.indent.len() - 2); 103 } 104 105 fn add_clip_id( 106 &mut self, 107 clip_id: ClipId 108 ) -> u64 { 109 let id = self.next_wrench_id; 110 self.next_wrench_id += 1; 111 112 let _prev = self.clip_id_map.insert(clip_id, id); 113 assert!(_prev.is_none()); 114 115 id 116 } 117 118 fn add_clipchain_id( 119 &mut self, 120 clipchain_id: ClipChainId 121 ) -> u64 { 122 let id = self.next_wrench_id; 123 self.next_wrench_id += 1; 124 125 let _prev = self.clipchain_id_map.insert(clipchain_id, id); 126 assert!(_prev.is_none()); 127 128 id 129 } 130 131 fn add_and_write_spatial_node( 132 &mut self, 133 spatial_id: SpatialId, 134 parent: SpatialId, 135 kind: SpatialNodeKind, 136 ) { 137 match kind { 138 SpatialNodeKind::Reference {} => { 139 self.write_line("- type: reference-frame"); 140 self.push_level(); 141 self.write_line(&format!("id: {}", self.next_wrench_id)); 142 if let Some(parent) = self.spatial_nodes.get(&parent) { 143 self.write_line(&format!("spatial-id: {}", parent.wrench_id)); 144 } 145 self.pop_level(); 146 } 147 SpatialNodeKind::Scroll {} => { 148 let parent_id = self.spatial_nodes[&parent].wrench_id; 149 150 self.write_line("- type: scroll-frame"); 151 self.push_level(); 152 self.write_line(&format!("id: {}", self.next_wrench_id)); 153 self.write_bounds(LayoutRect::zero()); 154 self.write_line(&format!("spatial-id: {}", parent_id)); 155 self.pop_level(); 156 } 157 SpatialNodeKind::Sticky {} => { 158 let parent_id = self.spatial_nodes[&parent].wrench_id; 159 160 self.write_line("- type: sticky-frame"); 161 self.push_level(); 162 self.write_line(&format!("id: {}", self.next_wrench_id)); 163 self.write_line(&format!("spatial-id: {}", parent_id)); 164 self.write_bounds(LayoutRect::zero()); 165 self.pop_level(); 166 } 167 } 168 169 let _prev = self.spatial_nodes.insert( 170 spatial_id, 171 SpatialNode { 172 wrench_id: self.next_wrench_id, 173 }, 174 ); 175 assert!(_prev.is_none()); 176 self.next_wrench_id += 1; 177 } 178 179 fn write_color( 180 &mut self, 181 color: ColorF, 182 ) { 183 self.write_line( 184 &format!("color: {}", color_to_string(color)) 185 ); 186 } 187 188 fn write_rect( 189 &mut self, 190 tag: &str, 191 rect: LayoutRect, 192 ) { 193 self.write_line( 194 &format!("{}: {} {} {} {}", 195 tag, 196 rect.min.x, 197 rect.min.y, 198 rect.width(), 199 rect.height(), 200 ) 201 ); 202 } 203 204 fn write_bounds( 205 &mut self, 206 bounds: LayoutRect, 207 ) { 208 self.write_rect("bounds", bounds); 209 } 210 211 fn maybe_write_clip_rect( 212 &mut self, 213 bounds: LayoutRect, 214 clip_rect: LayoutRect, 215 ) { 216 if bounds != clip_rect { 217 self.write_rect("clip-rect", clip_rect); 218 } 219 } 220 221 fn create_savepoint( 222 &mut self, 223 ) -> (usize, usize) { 224 (self.out.len(), self.indent.len()) 225 } 226 227 fn restore_savepoint( 228 &mut self, 229 p: (usize, usize), 230 ) { 231 self.out.truncate(p.0); 232 self.indent.truncate(p.1); 233 } 234 235 fn write_line( 236 &mut self, 237 s: &str, 238 ) { 239 self.out.push_str(&self.indent); 240 self.out.push_str(s); 241 self.out.push_str("\n"); 242 } 243 244 fn write_spatial_id( 245 &mut self, 246 id: SpatialId, 247 ) { 248 let spatial_node = self.spatial_nodes 249 .get(&id) 250 .expect(&format!("unknown spatial node {:?}", id)); 251 252 self.write_line(&format!("spatial-id: {}", spatial_node.wrench_id)); 253 } 254 255 fn write_clip_chain_id( 256 &mut self, 257 id: ClipChainId, 258 ) { 259 if id != ClipChainId::INVALID { 260 let clip_chain_id = self.clipchain_id_map[&id]; 261 self.write_line(&format!("clip-chain: {}", clip_chain_id)); 262 } 263 } 264 265 fn build_spatial_tree( 266 &mut self, 267 dl: &BuiltDisplayList, 268 pipeline_id: PipelineId, 269 ) { 270 // Insert root ref + scroll frames 271 self.add_and_write_spatial_node( 272 SpatialId::root_reference_frame(pipeline_id), 273 SpatialId::root_reference_frame(pipeline_id), 274 SpatialNodeKind::Reference { }, 275 ); 276 self.add_and_write_spatial_node( 277 SpatialId::root_scroll_node(pipeline_id), 278 SpatialId::root_reference_frame(pipeline_id), 279 SpatialNodeKind::Scroll { }, 280 ); 281 282 dl.iter_spatial_tree(|item| { 283 match item { 284 SpatialTreeItem::ScrollFrame(descriptor) => { 285 self.add_and_write_spatial_node( 286 descriptor.scroll_frame_id, 287 descriptor.parent_space, 288 SpatialNodeKind::Scroll { 289 }, 290 ); 291 } 292 SpatialTreeItem::ReferenceFrame(descriptor) => { 293 self.add_and_write_spatial_node( 294 descriptor.reference_frame.id, 295 descriptor.parent_spatial_id, 296 SpatialNodeKind::Reference { 297 }, 298 ); 299 } 300 SpatialTreeItem::StickyFrame(descriptor) => { 301 self.add_and_write_spatial_node( 302 descriptor.id, 303 descriptor.parent_spatial_id, 304 SpatialNodeKind::Sticky { 305 }, 306 ); 307 } 308 SpatialTreeItem::Invalid => { 309 unreachable!(); 310 } 311 } 312 }); 313 } 314 315 fn write_pipeline( 316 &mut self, 317 scene: &Scene, 318 pipeline_id: PipelineId, 319 ) -> Result<(), String> { 320 enum ContextKind { 321 Root, 322 StackingContext { 323 // sc_info: StackingContextInfo, 324 }, 325 } 326 struct BuildContext { 327 kind: ContextKind, 328 } 329 330 let pipeline = &scene.pipelines[&pipeline_id]; 331 332 self.build_spatial_tree( 333 &pipeline.display_list.display_list, 334 pipeline_id, 335 ); 336 337 let mut stack = vec![BuildContext { 338 kind: ContextKind::Root, 339 }]; 340 let mut traversal = pipeline.display_list.iter(); 341 342 'outer: while let Some(bc) = stack.pop() { 343 loop { 344 let item = match traversal.next() { 345 Some(item) => item, 346 None => break, 347 }; 348 349 match item.item() { 350 DisplayItem::PushStackingContext(info) => { 351 self.write_line("- type: stacking-context"); 352 self.push_level(); 353 self.write_spatial_id(info.spatial_id); 354 if let Some(clip_chain_id) = info.stacking_context.clip_chain_id { 355 self.write_clip_chain_id(clip_chain_id); 356 } 357 self.write_line( 358 &format!("bounds: {} {} 0 0", 359 0.0, //info.origin.x + info.ref_frame_offset.x, 360 0.0, //info.origin.y + info.ref_frame_offset.y, 361 ) 362 ); 363 self.write_line("items:"); 364 self.push_level(); 365 366 let new_context = BuildContext { 367 kind: ContextKind::StackingContext { 368 // sc_info, 369 }, 370 }; 371 stack.push(bc); 372 stack.push(new_context); 373 374 traversal = item.sub_iter(); 375 continue 'outer; 376 } 377 DisplayItem::PopStackingContext => { 378 self.pop_level(); 379 self.pop_level(); 380 } 381 DisplayItem::Iframe(info) => { 382 self.write_line("- type: iframe"); 383 self.push_level(); 384 self.write_spatial_id(info.space_and_clip.spatial_id); 385 self.write_clip_chain_id(info.space_and_clip.clip_chain_id); 386 self.write_bounds(info.bounds); 387 self.maybe_write_clip_rect(info.bounds, info.clip_rect); 388 self.write_line(&format!("id: [{}, {}]", 389 info.pipeline_id.0, 390 info.pipeline_id.1, 391 )); 392 self.pop_level(); 393 } 394 DisplayItem::Rectangle(info) => { 395 self.write_line("- type: rect"); 396 self.push_level(); 397 self.write_spatial_id(info.common.spatial_id); 398 self.write_clip_chain_id(info.common.clip_chain_id); 399 self.write_bounds(info.bounds); 400 self.maybe_write_clip_rect(info.bounds, info.common.clip_rect); 401 let color = match info.color { 402 PropertyBinding::Binding(..) => { 403 println!("WARN: Property color bindings are unsupported"); 404 ColorF::new(1.0, 0.0, 1.0, 0.5) 405 } 406 PropertyBinding::Value(color) => { 407 color 408 } 409 }; 410 if color.a > 0.0 { 411 self.write_color(color); 412 } 413 self.pop_level(); 414 } 415 DisplayItem::Text(info) => { 416 self.write_line("- type: rect"); 417 self.push_level(); 418 self.write_spatial_id(info.common.spatial_id); 419 self.write_clip_chain_id(info.common.clip_chain_id); 420 self.write_bounds(info.bounds); 421 self.maybe_write_clip_rect(info.bounds, info.common.clip_rect); 422 self.write_color(ColorF::new(1.0, 0.0, 0.0, 0.5)); 423 self.pop_level(); 424 } 425 DisplayItem::Border(info) => { 426 let sp = self.create_savepoint(); 427 428 self.write_line("- type: border"); 429 self.push_level(); 430 self.write_spatial_id(info.common.spatial_id); 431 self.write_clip_chain_id(info.common.clip_chain_id); 432 self.maybe_write_clip_rect(info.bounds, info.common.clip_rect); 433 self.write_bounds(info.bounds); 434 435 match info.details { 436 BorderDetails::Normal(border) => { 437 self.write_line("border-type: normal"); 438 439 let colors = [ 440 border.top.color, 441 border.right.color, 442 border.bottom.color, 443 border.left.color, 444 ]; 445 446 if colors.iter().all(|c| c.a == 0.0) { 447 self.restore_savepoint(sp); 448 continue; 449 } 450 451 if colors.iter().all(|c| *c == border.top.color) { 452 self.write_color(border.top.color); 453 } else { 454 self.write_line(&format!( 455 "color: [ [{}], [{}], [{}], [{}] ]", 456 color_to_string_array(colors[0]), 457 color_to_string_array(colors[1]), 458 color_to_string_array(colors[2]), 459 color_to_string_array(colors[3]), 460 ) 461 ); 462 } 463 464 let styles = [ 465 border.top.style, 466 border.right.style, 467 border.bottom.style, 468 border.left.style, 469 ]; 470 471 if styles.iter().all(|s| *s == border.top.style) { 472 self.write_line(&format!( 473 "style: {}", style_to_string(styles[0]), 474 ) 475 ); 476 } else { 477 self.write_line(&format!( 478 "style: [ {}, {}, {}, {} ]", 479 style_to_string(styles[0]), 480 style_to_string(styles[1]), 481 style_to_string(styles[2]), 482 style_to_string(styles[3]), 483 ) 484 ); 485 } 486 487 self.write_line("width: [1, 1, 1, 1]"); 488 489 if !border.radius.is_zero() { 490 self.write_line("radius: {"); 491 self.push_level(); 492 self.write_line( 493 &format!("top-left: [{}, {}],", 494 border.radius.top_left.width, 495 border.radius.top_left.height, 496 ) 497 ); 498 self.write_line( 499 &format!("top-right: [{}, {}],", 500 border.radius.top_right.width, 501 border.radius.top_right.height, 502 ) 503 ); 504 self.write_line( 505 &format!("bottom-left: [{}, {}],", 506 border.radius.bottom_left.width, 507 border.radius.bottom_left.height, 508 ) 509 ); 510 self.write_line( 511 &format!("bottom-right: [{}, {}],", 512 border.radius.bottom_right.width, 513 border.radius.bottom_right.height, 514 ) 515 ); 516 self.pop_level(); 517 self.write_line("}"); 518 } 519 } 520 BorderDetails::NinePatch(..) => { 521 todo!(); 522 } 523 } 524 525 self.pop_level(); 526 } 527 DisplayItem::Image(info) => { 528 self.write_line("- type: image"); 529 self.push_level(); 530 self.write_spatial_id(info.common.spatial_id); 531 self.write_clip_chain_id(info.common.clip_chain_id); 532 self.write_bounds(info.bounds); 533 self.maybe_write_clip_rect(info.bounds, info.common.clip_rect); 534 self.write_line( 535 &format!("src: checkerboard(2,8,8,{},{})", 536 ((info.bounds.width() - 2.0) / 8.0).ceil() as i32, 537 ((info.bounds.height() - 2.0) / 8.0).ceil() as i32, 538 ), 539 ); 540 self.pop_level(); 541 } 542 DisplayItem::RectClip(info) => { 543 let clip_id = self.add_clip_id(info.id); 544 self.write_line("- type: clip"); 545 self.push_level(); 546 self.write_line(&format!("id: {}", clip_id)); 547 self.write_spatial_id(info.spatial_id); 548 self.write_rect("bounds", info.clip_rect); 549 self.pop_level(); 550 } 551 DisplayItem::ImageMaskClip(info) => { 552 let clip_id = self.add_clip_id(info.id); 553 self.write_line("- type: clip"); 554 self.push_level(); 555 self.write_line(&format!("id: {}", clip_id)); 556 self.write_spatial_id(info.spatial_id); 557 self.write_rect("bounds", info.image_mask.rect); 558 self.pop_level(); 559 } 560 DisplayItem::RoundedRectClip(info) => { 561 let clip_id = self.add_clip_id(info.id); 562 self.write_line("- type: clip"); 563 self.push_level(); 564 self.write_line(&format!("id: {}", clip_id)); 565 self.write_spatial_id(info.spatial_id); 566 self.write_line("complex:"); 567 self.push_level(); 568 self.write_rect("- rect", info.clip.rect); 569 self.push_level(); 570 self.write_line("radius: {"); 571 self.push_level(); 572 self.write_line( 573 &format!("top-left: [{}, {}],", 574 info.clip.radii.top_left.width, 575 info.clip.radii.top_left.height, 576 )); 577 self.write_line( 578 &format!("top-right: [{}, {}],", 579 info.clip.radii.top_right.width, 580 info.clip.radii.top_right.height, 581 )); 582 self.write_line( 583 &format!("bottom-right: [{}, {}],", 584 info.clip.radii.bottom_right.width, 585 info.clip.radii.bottom_right.height, 586 )); 587 self.write_line( 588 &format!("bottom-left: [{}, {}],", 589 info.clip.radii.bottom_left.width, 590 info.clip.radii.bottom_left.height, 591 )); 592 self.pop_level(); 593 self.write_line("}"); 594 self.pop_level(); 595 self.pop_level(); 596 self.pop_level(); 597 } 598 DisplayItem::ClipChain(info) => { 599 let clipchain_id = self.add_clipchain_id(info.id); 600 self.write_line("- type: clip-chain"); 601 self.push_level(); 602 self.write_line(&format!("id: {}", clipchain_id)); 603 let mut clips = String::new(); 604 clips.push_str("clips: ["); 605 for id in item.clip_chain_items().iter() { 606 clips.push_str(&format!("{}, ", self.clip_id_map[&id])) 607 } 608 clips.push_str("]"); 609 self.write_line(&clips); 610 self.pop_level(); 611 } 612 613 // TODO(gw): Ignored elements - we should as support for 614 // these as needed. 615 DisplayItem::SetGradientStops => {} 616 DisplayItem::SetFilterOps => {} 617 DisplayItem::SetFilterData => {} 618 DisplayItem::SetPoints => {} 619 DisplayItem::PopAllShadows => {} 620 DisplayItem::ReuseItems(..) => {} 621 DisplayItem::RetainedItems(..) => {} 622 DisplayItem::RepeatingImage(..) => {} 623 DisplayItem::YuvImage(..) => {} 624 DisplayItem::BackdropFilter(..) => {} 625 DisplayItem::PushShadow(..) => {} 626 DisplayItem::Gradient(..) => {} 627 DisplayItem::RadialGradient(..) => {} 628 DisplayItem::ConicGradient(..) => {} 629 DisplayItem::Line(..) => {} 630 DisplayItem::HitTest(..) => {} 631 DisplayItem::PushReferenceFrame(..) => {} 632 DisplayItem::PopReferenceFrame => {} 633 DisplayItem::DebugMarker(..) => {} 634 DisplayItem::BoxShadow(..) => {} 635 }; 636 } 637 638 match bc.kind { 639 ContextKind::Root => {} 640 ContextKind::StackingContext { } => { 641 // self.pop_stacking_context(sc_info); 642 } 643 } 644 } 645 646 assert!(stack.is_empty()); 647 648 Ok(()) 649 } 650 651 fn write_scene( 652 mut self, 653 scene: &Scene 654 ) -> Result<String, String> { 655 self.write_line(&format!("# process-capture")); 656 self.write_line("---"); 657 self.write_line("root:"); 658 self.push_level(); 659 self.write_line("items:"); 660 self.push_level(); 661 662 if let Some(root_pipeline_id) = scene.root_pipeline_id { 663 self.write_pipeline(scene, root_pipeline_id)?; 664 } 665 666 self.pop_level(); 667 self.pop_level(); 668 assert!(self.indent.is_empty()); 669 670 if scene.pipelines.len() > 1 { 671 self.write_line("pipelines:"); 672 self.push_level(); 673 for (id, _) in &scene.pipelines { 674 if Some(*id) == scene.root_pipeline_id { 675 continue; 676 } 677 678 self.write_line(&format!("- id: [{}, {}]", id.0, id.1)); 679 self.push_level(); 680 self.write_line("items:"); 681 self.push_level(); 682 self.write_pipeline(scene, *id)?; 683 self.pop_level(); 684 self.pop_level(); 685 } 686 self.pop_level(); 687 } 688 689 Ok(self.out) 690 } 691 } 692 693 pub fn scene_to_yaml( 694 scene: &Scene, 695 ) -> Result<String, String> { 696 let writer = YamlWriter::new(); 697 698 writer.write_scene(scene) 699 }