yaml_frame_reader.rs (78342B)
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 euclid::SideOffsets2D; 6 use gleam::gl; 7 use image::GenericImageView; 8 use crate::parse_function::parse_function; 9 use crate::premultiply::premultiply; 10 use std::collections::HashMap; 11 use std::convert::TryInto; 12 use std::fs::File; 13 use std::io::Read; 14 use std::path::{Path, PathBuf}; 15 use std::usize; 16 use webrender::api::*; 17 use webrender::render_api::*; 18 use webrender::api::units::*; 19 use webrender::api::FillRule; 20 use crate::wrench::{FontDescriptor, Wrench, WrenchThing, DisplayList}; 21 use crate::yaml_helper::{StringEnum, YamlHelper, make_perspective}; 22 use yaml_rust::{Yaml, YamlLoader}; 23 use crate::PLATFORM_DEFAULT_FACE_NAME; 24 25 macro_rules! try_intersect { 26 ($first: expr, $second: expr) => { 27 if let Some(rect) = ($first).intersection($second) { 28 rect 29 } else { 30 warn!("skipping item with non-intersecting bounds and clip_rect"); 31 return; 32 } 33 } 34 } 35 36 fn rsrc_path(item: &Yaml, aux_dir: &Path) -> PathBuf { 37 let filename = item.as_str().unwrap(); 38 let mut file = aux_dir.to_path_buf(); 39 file.push(filename); 40 file 41 } 42 43 impl FontDescriptor { 44 fn from_yaml(item: &Yaml, aux_dir: &Path) -> FontDescriptor { 45 if !item["family"].is_badvalue() { 46 FontDescriptor::Properties { 47 family: item["family"].as_str().unwrap().to_owned(), 48 weight: item["weight"].as_i64().unwrap_or(400) as u32, 49 style: item["style"].as_i64().unwrap_or(0) as u32, 50 stretch: item["stretch"].as_i64().unwrap_or(5) as u32, 51 } 52 } else if !item["font"].is_badvalue() { 53 let path = rsrc_path(&item["font"], aux_dir); 54 FontDescriptor::Path { 55 path, 56 font_index: item["font-index"].as_i64().unwrap_or(0) as u32, 57 } 58 } else { 59 FontDescriptor::Family { 60 name: PLATFORM_DEFAULT_FACE_NAME.to_string(), 61 } 62 } 63 } 64 } 65 66 struct LocalExternalImageHandler { 67 texture_ids: Vec<(gl::GLuint, ImageDescriptor)>, 68 } 69 70 impl LocalExternalImageHandler { 71 pub fn new() -> LocalExternalImageHandler { 72 LocalExternalImageHandler { 73 texture_ids: Vec::new(), 74 } 75 } 76 77 fn init_gl_texture( 78 id: gl::GLuint, 79 gl_target: gl::GLuint, 80 format_desc: webrender::FormatDesc, 81 width: gl::GLint, 82 height: gl::GLint, 83 bytes: &[u8], 84 gl: &dyn gl::Gl, 85 ) { 86 gl.bind_texture(gl_target, id); 87 gl.tex_parameter_i(gl_target, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint); 88 gl.tex_parameter_i(gl_target, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint); 89 gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); 90 gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); 91 gl.tex_image_2d( 92 gl_target, 93 0, 94 format_desc.internal as gl::GLint, 95 width, 96 height, 97 0, 98 format_desc.external, 99 format_desc.pixel_type, 100 Some(bytes), 101 ); 102 gl.bind_texture(gl_target, 0); 103 } 104 105 pub fn add_image(&mut self, 106 device: &webrender::Device, 107 desc: ImageDescriptor, 108 target: ImageBufferKind, 109 image_data: ImageData, 110 ) -> ImageData { 111 let (image_id, channel_idx) = match image_data { 112 ImageData::Raw(ref data) => { 113 let gl = device.gl(); 114 let texture_ids = gl.gen_textures(1); 115 let format_desc = if desc.format == ImageFormat::BGRA8 { 116 // Force BGRA8 data to RGBA8 layout to avoid potential 117 // need for usage of texture-swizzle. 118 webrender::FormatDesc { 119 external: gl::BGRA, 120 .. device.gl_describe_format(ImageFormat::RGBA8) 121 } 122 } else { 123 device.gl_describe_format(desc.format) 124 }; 125 126 LocalExternalImageHandler::init_gl_texture( 127 texture_ids[0], 128 webrender::get_gl_target(target), 129 format_desc, 130 desc.size.width as gl::GLint, 131 desc.size.height as gl::GLint, 132 data, 133 gl, 134 ); 135 self.texture_ids.push((texture_ids[0], desc)); 136 (ExternalImageId((self.texture_ids.len() - 1) as u64), 0) 137 }, 138 _ => panic!("unsupported!"), 139 }; 140 141 ImageData::External( 142 ExternalImageData { 143 id: image_id, 144 channel_index: channel_idx, 145 image_type: ExternalImageType::TextureHandle(target), 146 normalized_uvs: false, 147 } 148 ) 149 } 150 } 151 152 impl ExternalImageHandler for LocalExternalImageHandler { 153 fn lock( 154 &mut self, 155 key: ExternalImageId, 156 _channel_index: u8, 157 _is_composited: bool, 158 ) -> ExternalImage<'_> { 159 let (id, desc) = self.texture_ids[key.0 as usize]; 160 ExternalImage { 161 uv: TexelRect::new(0.0, 0.0, desc.size.width as f32, desc.size.height as f32), 162 source: ExternalImageSource::NativeTexture(id), 163 } 164 } 165 fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} 166 } 167 168 fn broadcast<T: Clone>(base_vals: &[T], num_items: usize) -> Vec<T> { 169 if base_vals.len() == num_items { 170 return base_vals.to_vec(); 171 } 172 173 assert_eq!( 174 num_items % base_vals.len(), 175 0, 176 "Cannot broadcast {} elements into {}", 177 base_vals.len(), 178 num_items 179 ); 180 181 let mut vals = vec![]; 182 loop { 183 if vals.len() == num_items { 184 break; 185 } 186 vals.extend_from_slice(base_vals); 187 } 188 vals 189 } 190 191 enum CheckerboardKind { 192 BlackGrey, 193 BlackTransparent, 194 } 195 196 fn generate_checkerboard_image( 197 border: u32, 198 tile_x_size: u32, 199 tile_y_size: u32, 200 tile_x_count: u32, 201 tile_y_count: u32, 202 kind: CheckerboardKind, 203 ) -> (ImageDescriptor, ImageData) { 204 let width = 2 * border + tile_x_size * tile_x_count; 205 let height = 2 * border + tile_y_size * tile_y_count; 206 let mut pixels = Vec::new(); 207 208 for y in 0 .. height { 209 for x in 0 .. width { 210 if y < border || y >= (height - border) || 211 x < border || x >= (width - border) { 212 pixels.push(0); 213 pixels.push(0); 214 pixels.push(0xff); 215 pixels.push(0xff); 216 } else { 217 let xon = ((x - border) % (2 * tile_x_size)) < tile_x_size; 218 let yon = ((y - border) % (2 * tile_y_size)) < tile_y_size; 219 match kind { 220 CheckerboardKind::BlackGrey => { 221 let value = if xon ^ yon { 0xff } else { 0x7f }; 222 pixels.push(value); 223 pixels.push(value); 224 pixels.push(value); 225 pixels.push(0xff); 226 } 227 CheckerboardKind::BlackTransparent => { 228 let value = if xon ^ yon { 0xff } else { 0x00 }; 229 pixels.push(value); 230 pixels.push(value); 231 pixels.push(value); 232 pixels.push(value); 233 } 234 } 235 } 236 } 237 } 238 239 let flags = match kind { 240 CheckerboardKind::BlackGrey => ImageDescriptorFlags::IS_OPAQUE, 241 CheckerboardKind::BlackTransparent => ImageDescriptorFlags::empty(), 242 }; 243 244 ( 245 ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, flags), 246 ImageData::new(pixels), 247 ) 248 } 249 250 fn generate_xy_gradient_image(w: u32, h: u32) -> (ImageDescriptor, ImageData) { 251 let mut pixels = Vec::with_capacity((w * h * 4) as usize); 252 for y in 0 .. h { 253 for x in 0 .. w { 254 let grid = if x % 100 < 3 || y % 100 < 3 { 0.9 } else { 1.0 }; 255 pixels.push((y as f32 / h as f32 * 255.0 * grid) as u8); 256 pixels.push(0); 257 pixels.push((x as f32 / w as f32 * 255.0 * grid) as u8); 258 pixels.push(255); 259 } 260 } 261 262 ( 263 ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), 264 ImageData::new(pixels), 265 ) 266 } 267 268 fn generate_solid_color_image( 269 r: u8, 270 g: u8, 271 b: u8, 272 a: u8, 273 w: u32, 274 h: u32, 275 ) -> (ImageDescriptor, ImageData) { 276 let num_pixels: usize = (w * h).try_into().unwrap(); 277 let pixels = [b, g, r, a].repeat(num_pixels); 278 279 let mut flags = ImageDescriptorFlags::empty(); 280 if a == 255 { 281 flags |= ImageDescriptorFlags::IS_OPAQUE; 282 } 283 284 ( 285 ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, flags), 286 ImageData::new(pixels), 287 ) 288 } 289 290 291 292 fn is_image_opaque(format: ImageFormat, bytes: &[u8]) -> bool { 293 match format { 294 ImageFormat::BGRA8 | 295 ImageFormat::RGBA8 => { 296 let mut is_opaque = true; 297 for i in 0 .. (bytes.len() / 4) { 298 if bytes[i * 4 + 3] != 255 { 299 is_opaque = false; 300 break; 301 } 302 } 303 is_opaque 304 } 305 ImageFormat::RG8 => true, 306 ImageFormat::RG16 => true, 307 ImageFormat::R8 => false, 308 ImageFormat::R16 => false, 309 ImageFormat::RGBAF32 | 310 ImageFormat::RGBAI32 => unreachable!(), 311 } 312 } 313 314 struct IsRoot(bool); 315 316 pub struct Snapshot { 317 key: SnapshotImageKey, 318 size: LayoutSize, 319 } 320 321 pub struct YamlFrameReader { 322 yaml_path: PathBuf, 323 aux_dir: PathBuf, 324 frame_count: u32, 325 326 display_lists: Vec<DisplayList>, 327 328 watch_source: bool, 329 list_resources: bool, 330 331 /// A HashMap of offsets which specify what scroll offsets particular 332 /// scroll layers should be initialized with. 333 scroll_offsets: HashMap<ExternalScrollId, Vec<SampledScrollOffset>>, 334 next_external_scroll_id: u64, 335 336 image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>, 337 338 fonts: HashMap<FontDescriptor, FontKey>, 339 font_instances: HashMap<(FontKey, FontSize, FontInstanceFlags, SyntheticItalics), FontInstanceKey>, 340 font_render_mode: Option<FontRenderMode>, 341 snapshots: HashMap<String, Snapshot>, 342 allow_mipmaps: bool, 343 344 /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML 345 /// and having each of those ids correspond to a unique ClipId. 346 user_clip_id_map: HashMap<u64, ClipId>, 347 user_clipchain_id_map: HashMap<u64, ClipChainId>, 348 user_spatial_id_map: HashMap<u64, SpatialId>, 349 350 spatial_id_stack: Vec<SpatialId>, 351 352 requested_frame: usize, 353 built_frame: usize, 354 355 yaml_string: String, 356 keyframes: Option<Yaml>, 357 358 external_image_handler: Option<Box<LocalExternalImageHandler>>, 359 360 next_spatial_key: u64, 361 } 362 363 impl YamlFrameReader { 364 pub fn new(yaml_path: &Path) -> YamlFrameReader { 365 YamlFrameReader { 366 watch_source: false, 367 list_resources: false, 368 yaml_path: yaml_path.to_owned(), 369 aux_dir: yaml_path.parent().unwrap().to_owned(), 370 frame_count: 0, 371 display_lists: Vec::new(), 372 scroll_offsets: HashMap::new(), 373 fonts: HashMap::new(), 374 font_instances: HashMap::new(), 375 font_render_mode: None, 376 snapshots: HashMap::new(), 377 allow_mipmaps: false, 378 image_map: HashMap::new(), 379 user_clip_id_map: HashMap::new(), 380 user_clipchain_id_map: HashMap::new(), 381 user_spatial_id_map: HashMap::new(), 382 spatial_id_stack: Vec::new(), 383 yaml_string: String::new(), 384 requested_frame: 0, 385 built_frame: usize::MAX, 386 keyframes: None, 387 external_image_handler: Some(Box::new(LocalExternalImageHandler::new())), 388 next_external_scroll_id: 1000, // arbitrary to easily see in logs which are implicit 389 next_spatial_key: 0, 390 } 391 } 392 393 pub fn deinit(mut self, wrench: &mut Wrench) { 394 let mut txn = Transaction::new(); 395 396 for (_, font_instance) in self.font_instances.drain() { 397 txn.delete_font_instance(font_instance); 398 } 399 400 for (_, font) in self.fonts.drain() { 401 txn.delete_font(font); 402 } 403 404 wrench.api.send_transaction(wrench.document_id, txn); 405 } 406 407 fn top_space(&self) -> SpatialId { 408 *self.spatial_id_stack.last().unwrap() 409 } 410 411 pub fn yaml_path(&self) -> &PathBuf { 412 &self.yaml_path 413 } 414 415 pub fn new_from_args(args: &clap::ArgMatches) -> YamlFrameReader { 416 let yaml_file = args.value_of("INPUT").map(PathBuf::from).unwrap(); 417 418 let mut y = YamlFrameReader::new(&yaml_file); 419 420 y.keyframes = args.value_of("keyframes").map(|path| { 421 let mut file = File::open(&path).unwrap(); 422 let mut keyframes_string = String::new(); 423 file.read_to_string(&mut keyframes_string).unwrap(); 424 YamlLoader::load_from_str(&keyframes_string) 425 .expect("Failed to parse keyframes file") 426 .pop() 427 .unwrap() 428 }); 429 y.list_resources = args.is_present("list-resources"); 430 y.watch_source = args.is_present("watch"); 431 y 432 } 433 434 pub fn reset(&mut self) { 435 self.scroll_offsets.clear(); 436 self.display_lists.clear(); 437 } 438 439 fn build(&mut self, wrench: &mut Wrench) { 440 let yaml = YamlLoader::load_from_str(&self.yaml_string) 441 .map(|mut yaml| { 442 assert_eq!(yaml.len(), 1); 443 yaml.pop().unwrap() 444 }) 445 .expect("Failed to parse YAML file"); 446 447 self.reset(); 448 449 if let Some(pipelines) = yaml["pipelines"].as_vec() { 450 for pipeline in pipelines { 451 let pipeline_id = pipeline["id"].as_pipeline_id().unwrap(); 452 let mut builder = DisplayListBuilder::new(pipeline_id); 453 self.build_pipeline(wrench, &mut builder, pipeline_id, false, pipeline); 454 } 455 } 456 457 let mut builder = DisplayListBuilder::new(wrench.root_pipeline_id); 458 459 if let Some(frames) = yaml["frames"].as_vec() { 460 for frame in frames { 461 self.build_pipeline(wrench, &mut builder, wrench.root_pipeline_id, true, frame); 462 } 463 } else { 464 let root_stacking_context = &yaml["root"]; 465 assert_ne!(*root_stacking_context, Yaml::BadValue); 466 self.build_pipeline(wrench, &mut builder, wrench.root_pipeline_id, true, root_stacking_context); 467 } 468 469 // If replaying the same frame during interactive use, the frame gets rebuilt, 470 // but the external image handler has already been consumed by the renderer. 471 if let Some(external_image_handler) = self.external_image_handler.take() { 472 wrench.renderer.set_external_image_handler(external_image_handler); 473 } 474 } 475 476 fn build_pipeline( 477 &mut self, 478 wrench: &mut Wrench, 479 builder: &mut DisplayListBuilder, 480 pipeline_id: PipelineId, 481 send_transaction: bool, 482 yaml: &Yaml 483 ) { 484 let offscreen = yaml["offscreen"].as_bool().unwrap_or(false); 485 // By default, present if send_transaction is set to true. Can be overridden 486 // by a field in the pipeline's root. 487 let present = !offscreen && yaml["present"].as_bool().unwrap_or(send_transaction); 488 489 // Don't allow referencing clips between pipelines for now. 490 self.user_clip_id_map.clear(); 491 self.user_clipchain_id_map.clear(); 492 self.user_spatial_id_map.clear(); 493 self.spatial_id_stack.clear(); 494 self.spatial_id_stack.push(SpatialId::root_scroll_node(pipeline_id)); 495 496 builder.begin(); 497 let mut info = CommonItemProperties { 498 clip_rect: LayoutRect::zero(), 499 clip_chain_id: ClipChainId::INVALID, 500 spatial_id: SpatialId::new(0, PipelineId::dummy()), 501 flags: PrimitiveFlags::default(), 502 }; 503 self.add_stacking_context_from_yaml(builder, wrench, yaml, IsRoot(true), &mut info); 504 let (pipeline, payload) = builder.end(); 505 self.display_lists.push(DisplayList { 506 pipeline, 507 payload, 508 present, 509 send_transaction, 510 render_offscreen: offscreen, 511 }); 512 513 assert_eq!(self.spatial_id_stack.len(), 1); 514 } 515 516 fn to_clip_chain_id( 517 &self, 518 item: &Yaml, 519 builder: &mut DisplayListBuilder, 520 ) -> Option<ClipChainId> { 521 match *item { 522 Yaml::Integer(value) => { 523 Some(self.user_clipchain_id_map[&(value as u64)]) 524 } 525 Yaml::Array(ref array) => { 526 let clip_ids: Vec<ClipId> = array 527 .iter() 528 .map(|id| { 529 let id = id.as_i64().expect("invalid clip id") as u64; 530 self.user_clip_id_map[&id] 531 }) 532 .collect(); 533 534 Some(builder.define_clip_chain(None, clip_ids)) 535 } 536 _ => None, 537 } 538 } 539 540 fn to_spatial_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<SpatialId> { 541 match *item { 542 Yaml::Integer(value) => Some(self.user_spatial_id_map[&(value as u64)]), 543 Yaml::String(ref id_string) if id_string == "root-reference-frame" => 544 Some(SpatialId::root_reference_frame(pipeline_id)), 545 Yaml::String(ref id_string) if id_string == "root-scroll-node" => 546 Some(SpatialId::root_scroll_node(pipeline_id)), 547 Yaml::BadValue => None, 548 _ => { 549 println!("Unable to parse SpatialId {:?}", item); 550 None 551 } 552 } 553 } 554 555 fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) { 556 assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip"); 557 self.user_clip_id_map.insert(numeric_id, real_id); 558 } 559 560 fn add_clip_chain_id_mapping(&mut self, numeric_id: u64, real_id: ClipChainId) { 561 assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip-chain"); 562 self.user_clipchain_id_map.insert(numeric_id, real_id); 563 } 564 565 fn add_spatial_id_mapping(&mut self, numeric_id: u64, real_id: SpatialId) { 566 assert_ne!(numeric_id, 0, "id=0 is reserved for the root reference frame"); 567 assert_ne!(numeric_id, 1, "id=1 is reserved for the root scroll node"); 568 self.user_spatial_id_map.insert(numeric_id, real_id); 569 } 570 571 fn to_hit_testing_tag(&self, item: &Yaml) -> Option<ItemTag> { 572 match *item { 573 Yaml::Array(ref array) if array.len() == 2 => { 574 match (array[0].as_i64(), array[1].as_i64()) { 575 (Some(first), Some(second)) => Some((first as u64, second as u16)), 576 _ => None, 577 } 578 } 579 _ => None, 580 } 581 582 } 583 584 fn add_or_get_image( 585 &mut self, 586 file: &Path, 587 tiling: Option<i64>, 588 item: &Yaml, 589 wrench: &mut Wrench, 590 ) -> (ImageKey, LayoutSize) { 591 let key = (file.to_owned(), tiling); 592 if let Some(k) = self.image_map.get(&key) { 593 return *k; 594 } 595 596 if self.list_resources { println!("{}", file.to_string_lossy()); } 597 let (descriptor, image_data) = match image::open(file) { 598 Ok(image) => { 599 let (image_width, image_height) = image.dimensions(); 600 let (format, bytes) = match image { 601 image::DynamicImage::ImageLuma8(_) => { 602 (ImageFormat::R8, image.to_bytes()) 603 } 604 image::DynamicImage::ImageRgba8(_) => { 605 let mut pixels = image.to_bytes(); 606 premultiply(pixels.as_mut_slice()); 607 (ImageFormat::BGRA8, pixels) 608 } 609 image::DynamicImage::ImageRgb8(_) => { 610 let bytes = image.to_bytes(); 611 let mut pixels = Vec::with_capacity(image_width as usize * image_height as usize * 4); 612 for bgr in bytes.chunks(3) { 613 pixels.extend_from_slice(&[ 614 bgr[2], 615 bgr[1], 616 bgr[0], 617 0xff 618 ]); 619 } 620 (ImageFormat::BGRA8, pixels) 621 } 622 _ => panic!("We don't support whatever your crazy image type is, come on"), 623 }; 624 let mut flags = ImageDescriptorFlags::empty(); 625 if is_image_opaque(format, &bytes[..]) { 626 flags |= ImageDescriptorFlags::IS_OPAQUE; 627 } 628 if self.allow_mipmaps { 629 flags |= ImageDescriptorFlags::ALLOW_MIPMAPS; 630 } 631 let descriptor = ImageDescriptor::new( 632 image_width as i32, 633 image_height as i32, 634 format, 635 flags, 636 ); 637 let data = ImageData::new(bytes); 638 (descriptor, data) 639 } 640 _ => { 641 // This is a hack but it is convenient when generating test cases and avoids 642 // bloating the repository. 643 match parse_function( 644 file.components() 645 .last() 646 .unwrap() 647 .as_os_str() 648 .to_str() 649 .unwrap(), 650 ) { 651 ("xy-gradient", args, _) => generate_xy_gradient_image( 652 args.get(0).unwrap_or(&"1000").parse::<u32>().unwrap(), 653 args.get(1).unwrap_or(&"1000").parse::<u32>().unwrap(), 654 ), 655 ("solid-color", args, _) => generate_solid_color_image( 656 args.get(0).unwrap_or(&"255").parse::<u8>().unwrap(), 657 args.get(1).unwrap_or(&"255").parse::<u8>().unwrap(), 658 args.get(2).unwrap_or(&"255").parse::<u8>().unwrap(), 659 args.get(3).unwrap_or(&"255").parse::<u8>().unwrap(), 660 args.get(4).unwrap_or(&"1000").parse::<u32>().unwrap(), 661 args.get(5).unwrap_or(&"1000").parse::<u32>().unwrap(), 662 ), 663 (name @ "transparent-checkerboard", args, _) | 664 (name @ "checkerboard", args, _) => { 665 let border = args.get(0).unwrap_or(&"4").parse::<u32>().unwrap(); 666 667 let (x_size, y_size, x_count, y_count) = match args.len() { 668 3 => { 669 let size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap(); 670 let count = args.get(2).unwrap_or(&"8").parse::<u32>().unwrap(); 671 (size, size, count, count) 672 } 673 5 => { 674 let x_size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap(); 675 let y_size = args.get(2).unwrap_or(&"32").parse::<u32>().unwrap(); 676 let x_count = args.get(3).unwrap_or(&"8").parse::<u32>().unwrap(); 677 let y_count = args.get(4).unwrap_or(&"8").parse::<u32>().unwrap(); 678 (x_size, y_size, x_count, y_count) 679 } 680 _ => { 681 panic!("invalid checkerboard function"); 682 } 683 }; 684 685 let kind = if name == "transparent-checkerboard" { 686 CheckerboardKind::BlackTransparent 687 } else { 688 CheckerboardKind::BlackGrey 689 }; 690 691 generate_checkerboard_image( 692 border, 693 x_size, 694 y_size, 695 x_count, 696 y_count, 697 kind, 698 ) 699 } 700 ("snapshot", args, _) => { 701 let snapshot = self.snapshots 702 .get(args[0]) 703 .expect("Missing snapshot"); 704 return ( 705 snapshot.key.as_image(), 706 snapshot.size, 707 ); 708 } 709 _ => { 710 panic!("Failed to load image {:?}", file.to_str()); 711 } 712 } 713 } 714 }; 715 let tiling = tiling.map(|tile_size| tile_size as u16); 716 let image_key = wrench.api.generate_image_key(); 717 let mut txn = Transaction::new(); 718 719 let external = item["external"].as_bool().unwrap_or(false); 720 if external { 721 // This indicates we want to simulate an external texture, 722 // ensure it gets created as such 723 let external_target = match item["external-target"].as_str() { 724 Some("2d") => ImageBufferKind::Texture2D, 725 Some("rect") => ImageBufferKind::TextureRect, 726 Some(t) => panic!("Unsupported external texture target: {}", t), 727 None => ImageBufferKind::Texture2D, 728 }; 729 730 let external_image_data = 731 self.external_image_handler.as_mut().unwrap().add_image( 732 &wrench.renderer.device, 733 descriptor, 734 external_target, 735 image_data 736 ); 737 txn.add_image(image_key, descriptor, external_image_data, tiling); 738 } else { 739 txn.add_image(image_key, descriptor, image_data, tiling); 740 } 741 742 wrench.api.send_transaction(wrench.document_id, txn); 743 let val = ( 744 image_key, 745 LayoutSize::new(descriptor.size.width as f32, descriptor.size.height as f32), 746 ); 747 self.image_map.insert(key, val); 748 val 749 } 750 751 fn get_or_create_font(&mut self, desc: FontDescriptor, wrench: &mut Wrench) -> FontKey { 752 let list_resources = self.list_resources; 753 *self.fonts 754 .entry(desc.clone()) 755 .or_insert_with(|| match desc { 756 FontDescriptor::Path { 757 ref path, 758 font_index, 759 } => { 760 if list_resources { println!("{}", path.to_string_lossy()); } 761 let mut file = File::open(path).expect("Couldn't open font file"); 762 let mut bytes = vec![]; 763 file.read_to_end(&mut bytes) 764 .expect("failed to read font file"); 765 wrench.font_key_from_bytes(bytes, font_index) 766 } 767 FontDescriptor::Family { ref name } => wrench.font_key_from_name(name), 768 FontDescriptor::Properties { 769 ref family, 770 weight, 771 style, 772 stretch, 773 } => wrench.font_key_from_properties(family, weight, style, stretch), 774 }) 775 } 776 777 pub fn allow_mipmaps(&mut self, allow_mipmaps: bool) { 778 self.allow_mipmaps = allow_mipmaps; 779 } 780 781 pub fn set_font_render_mode(&mut self, render_mode: Option<FontRenderMode>) { 782 self.font_render_mode = render_mode; 783 } 784 785 fn get_or_create_font_instance( 786 &mut self, 787 font_key: FontKey, 788 size: f32, 789 flags: FontInstanceFlags, 790 synthetic_italics: SyntheticItalics, 791 wrench: &mut Wrench, 792 ) -> FontInstanceKey { 793 let font_render_mode = self.font_render_mode; 794 795 *self.font_instances 796 .entry((font_key, size.into(), flags, synthetic_italics)) 797 .or_insert_with(|| { 798 wrench.add_font_instance( 799 font_key, 800 size, 801 flags, 802 font_render_mode, 803 synthetic_italics, 804 ) 805 }) 806 } 807 808 fn as_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option<ImageMask> { 809 item.as_hash()?; 810 811 let tiling = item["tile-size"].as_i64(); 812 813 let (image_key, image_dims) = match item["image"].as_str() { 814 Some("invalid") => (ImageKey::DUMMY, LayoutSize::new(100.0, 100.0)), 815 Some(filename) => { 816 let mut file = self.aux_dir.clone(); 817 file.push(filename); 818 self.add_or_get_image(&file, tiling, item, wrench) 819 } 820 None => { 821 warn!("No image provided for the image-mask!"); 822 return None; 823 } 824 }; 825 826 let image_rect = item["rect"] 827 .as_rect() 828 .unwrap_or_else(|| LayoutRect::from_size(image_dims)); 829 Some(ImageMask { 830 image: image_key, 831 rect: image_rect, 832 }) 833 } 834 835 fn handle_rect( 836 &self, 837 dl: &mut DisplayListBuilder, 838 item: &Yaml, 839 info: &CommonItemProperties, 840 ) { 841 let bounds_key = if item["type"].is_badvalue() { 842 "rect" 843 } else { 844 "bounds" 845 }; 846 847 let bounds = self.resolve_rect(&item[bounds_key]); 848 let color = self.resolve_colorf(&item["color"]).unwrap_or(ColorF::BLACK); 849 dl.push_rect(info, bounds, color); 850 } 851 852 fn handle_hit_test( 853 &mut self, 854 dl: &mut DisplayListBuilder, 855 item: &Yaml, 856 info: &mut CommonItemProperties, 857 ) { 858 info.clip_rect = try_intersect!( 859 item["bounds"].as_rect().expect("hit-test type must have bounds"), 860 &info.clip_rect 861 ); 862 863 if let Some(tag) = self.to_hit_testing_tag(&item["hit-testing-tag"]) { 864 dl.push_hit_test( 865 info.clip_rect, 866 info.clip_chain_id, 867 info.spatial_id, 868 info.flags, 869 tag, 870 ); 871 } 872 } 873 874 fn handle_line( 875 &mut self, 876 dl: &mut DisplayListBuilder, 877 item: &Yaml, 878 info: &mut CommonItemProperties, 879 ) { 880 let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK); 881 let orientation = item["orientation"] 882 .as_str() 883 .and_then(LineOrientation::from_str) 884 .expect("line must have orientation"); 885 let style = item["style"] 886 .as_str() 887 .and_then(LineStyle::from_str) 888 .expect("line must have style"); 889 890 let wavy_line_thickness = if let LineStyle::Wavy = style { 891 item["thickness"].as_f32().expect("wavy lines must have a thickness") 892 } else { 893 0.0 894 }; 895 896 let area = if item["baseline"].is_badvalue() { 897 let bounds_key = if item["type"].is_badvalue() { 898 "rect" 899 } else { 900 "bounds" 901 }; 902 903 item[bounds_key] 904 .as_rect() 905 .expect("line type must have bounds") 906 } else { 907 // Legacy line representation 908 let baseline = item["baseline"].as_f32().expect("line must have baseline"); 909 let start = item["start"].as_f32().expect("line must have start"); 910 let end = item["end"].as_f32().expect("line must have end"); 911 let width = item["width"].as_f32().expect("line must have width"); 912 913 match orientation { 914 LineOrientation::Horizontal => { 915 LayoutRect::from_origin_and_size( 916 LayoutPoint::new(start, baseline), 917 LayoutSize::new(end - start, width), 918 ) 919 } 920 LineOrientation::Vertical => { 921 LayoutRect::from_origin_and_size( 922 LayoutPoint::new(baseline, start), 923 LayoutSize::new(width, end - start), 924 ) 925 } 926 } 927 }; 928 929 dl.push_line( 930 info, 931 &area, 932 wavy_line_thickness, 933 orientation, 934 &color, 935 style, 936 ); 937 } 938 939 fn handle_gradient( 940 &mut self, 941 dl: &mut DisplayListBuilder, 942 item: &Yaml, 943 info: &mut CommonItemProperties, 944 ) { 945 let bounds_key = if item["type"].is_badvalue() { 946 "gradient" 947 } else { 948 "bounds" 949 }; 950 let bounds = item[bounds_key] 951 .as_rect() 952 .expect("gradient must have bounds"); 953 954 let gradient = item.as_gradient(dl); 955 let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); 956 let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); 957 958 dl.push_gradient( 959 info, 960 bounds, 961 gradient, 962 tile_size, 963 tile_spacing 964 ); 965 } 966 967 fn handle_radial_gradient( 968 &mut self, 969 dl: &mut DisplayListBuilder, 970 item: &Yaml, 971 info: &mut CommonItemProperties, 972 ) { 973 let bounds_key = if item["type"].is_badvalue() { 974 "radial-gradient" 975 } else { 976 "bounds" 977 }; 978 let bounds = item[bounds_key] 979 .as_rect() 980 .expect("radial gradient must have bounds"); 981 let gradient = item.as_radial_gradient(dl); 982 let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); 983 let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); 984 985 dl.push_radial_gradient( 986 info, 987 bounds, 988 gradient, 989 tile_size, 990 tile_spacing, 991 ); 992 } 993 994 fn handle_conic_gradient( 995 &mut self, 996 dl: &mut DisplayListBuilder, 997 item: &Yaml, 998 info: &mut CommonItemProperties, 999 ) { 1000 let bounds_key = if item["type"].is_badvalue() { 1001 "conic-gradient" 1002 } else { 1003 "bounds" 1004 }; 1005 let bounds = item[bounds_key] 1006 .as_rect() 1007 .expect("conic gradient must have bounds"); 1008 let gradient = item.as_conic_gradient(dl); 1009 let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); 1010 let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); 1011 1012 dl.push_conic_gradient( 1013 info, 1014 bounds, 1015 gradient, 1016 tile_size, 1017 tile_spacing, 1018 ); 1019 } 1020 1021 fn handle_border( 1022 &mut self, 1023 dl: &mut DisplayListBuilder, 1024 wrench: &mut Wrench, 1025 item: &Yaml, 1026 info: &mut CommonItemProperties, 1027 ) { 1028 let bounds_key = if item["type"].is_badvalue() { 1029 "border" 1030 } else { 1031 "bounds" 1032 }; 1033 let bounds = item[bounds_key].as_rect().expect("borders must have bounds"); 1034 let widths = item["width"] 1035 .as_vec_f32() 1036 .expect("borders must have width(s)"); 1037 let widths = broadcast(&widths, 4); 1038 let widths = LayoutSideOffsets::new(widths[0], widths[3], widths[2], widths[1]); 1039 let border_details = if let Some(border_type) = item["border-type"].as_str() { 1040 match border_type { 1041 "normal" => { 1042 let colors = item["color"] 1043 .as_vec_colorf() 1044 .expect("borders must have color(s)"); 1045 let styles = item["style"] 1046 .as_vec_string() 1047 .expect("borders must have style(s)"); 1048 let styles = styles 1049 .iter() 1050 .map(|s| match s.as_str() { 1051 "none" => BorderStyle::None, 1052 "solid" => BorderStyle::Solid, 1053 "double" => BorderStyle::Double, 1054 "dotted" => BorderStyle::Dotted, 1055 "dashed" => BorderStyle::Dashed, 1056 "hidden" => BorderStyle::Hidden, 1057 "ridge" => BorderStyle::Ridge, 1058 "inset" => BorderStyle::Inset, 1059 "outset" => BorderStyle::Outset, 1060 "groove" => BorderStyle::Groove, 1061 s => { 1062 panic!("Unknown border style '{}'", s); 1063 } 1064 }) 1065 .collect::<Vec<BorderStyle>>(); 1066 let radius = item["radius"] 1067 .as_border_radius() 1068 .unwrap_or_else(BorderRadius::zero); 1069 1070 let colors = broadcast(&colors, 4); 1071 let styles = broadcast(&styles, 4); 1072 1073 let top = BorderSide { 1074 color: colors[0], 1075 style: styles[0], 1076 }; 1077 let right = BorderSide { 1078 color: colors[1], 1079 style: styles[1], 1080 }; 1081 let bottom = BorderSide { 1082 color: colors[2], 1083 style: styles[2], 1084 }; 1085 let left = BorderSide { 1086 color: colors[3], 1087 style: styles[3], 1088 }; 1089 let do_aa = item["do_aa"].as_bool().unwrap_or(true); 1090 Some(BorderDetails::Normal(NormalBorder { 1091 top, 1092 left, 1093 bottom, 1094 right, 1095 radius, 1096 do_aa, 1097 })) 1098 } 1099 "image" | "gradient" | "radial-gradient" | "conic-gradient" => { 1100 let image_width = item["image-width"] 1101 .as_i64() 1102 .unwrap_or(bounds.width() as i64); 1103 let image_height = item["image-height"] 1104 .as_i64() 1105 .unwrap_or(bounds.height() as i64); 1106 let fill = item["fill"].as_bool().unwrap_or(false); 1107 1108 let slice = if let Some(slice) = item["slice"].as_vec_u32() { 1109 broadcast(&slice, 4) 1110 } else { 1111 vec![widths.top as u32, widths.left as u32, widths.bottom as u32, widths.right as u32] 1112 }; 1113 1114 let repeat_horizontal = match item["repeat-horizontal"] 1115 .as_str() 1116 .unwrap_or("stretch") 1117 { 1118 "stretch" => RepeatMode::Stretch, 1119 "repeat" => RepeatMode::Repeat, 1120 "round" => RepeatMode::Round, 1121 "space" => RepeatMode::Space, 1122 s => panic!("Unknown box border image repeat mode {}", s), 1123 }; 1124 let repeat_vertical = match item["repeat-vertical"] 1125 .as_str() 1126 .unwrap_or("stretch") 1127 { 1128 "stretch" => RepeatMode::Stretch, 1129 "repeat" => RepeatMode::Repeat, 1130 "round" => RepeatMode::Round, 1131 "space" => RepeatMode::Space, 1132 s => panic!("Unknown box border image repeat mode {}", s), 1133 }; 1134 let source = match border_type { 1135 "image" => { 1136 let file = rsrc_path(&item["image-source"], &self.aux_dir); 1137 let (image_key, _) = self 1138 .add_or_get_image(&file, None, item, wrench); 1139 NinePatchBorderSource::Image(image_key, ImageRendering::Auto) 1140 } 1141 "gradient" => { 1142 let gradient = item.as_gradient(dl); 1143 NinePatchBorderSource::Gradient(gradient) 1144 } 1145 "radial-gradient" => { 1146 let gradient = item.as_radial_gradient(dl); 1147 NinePatchBorderSource::RadialGradient(gradient) 1148 } 1149 "conic-gradient" => { 1150 let gradient = item.as_conic_gradient(dl); 1151 NinePatchBorderSource::ConicGradient(gradient) 1152 } 1153 _ => unreachable!("Unexpected border type"), 1154 }; 1155 1156 Some(BorderDetails::NinePatch(NinePatchBorder { 1157 source, 1158 width: image_width as i32, 1159 height: image_height as i32, 1160 slice: SideOffsets2D::new(slice[0] as i32, slice[1] as i32, slice[2] as i32, slice[3] as i32), 1161 fill, 1162 repeat_horizontal, 1163 repeat_vertical, 1164 })) 1165 } 1166 _ => { 1167 println!("Unable to parse border {:?}", item); 1168 None 1169 } 1170 } 1171 } else { 1172 println!("Unable to parse border {:?}", item); 1173 None 1174 }; 1175 if let Some(details) = border_details { 1176 dl.push_border(info, bounds, widths, details); 1177 } 1178 } 1179 1180 fn handle_box_shadow( 1181 &mut self, 1182 dl: &mut DisplayListBuilder, 1183 item: &Yaml, 1184 info: &mut CommonItemProperties, 1185 ) { 1186 let bounds_key = if item["type"].is_badvalue() { 1187 "box-shadow" 1188 } else { 1189 "bounds" 1190 }; 1191 let bounds = item[bounds_key] 1192 .as_rect() 1193 .expect("box shadow must have bounds"); 1194 let box_bounds = item["box-bounds"].as_rect().unwrap_or(bounds); 1195 let offset = self.resolve_vector(&item["offset"], LayoutVector2D::zero()); 1196 let color = item["color"] 1197 .as_colorf() 1198 .unwrap_or_else(|| ColorF::new(0.0, 0.0, 0.0, 1.0)); 1199 let blur_radius = item["blur-radius"].as_force_f32().unwrap_or(0.0); 1200 let spread_radius = item["spread-radius"].as_force_f32().unwrap_or(0.0); 1201 let border_radius = item["border-radius"] 1202 .as_border_radius() 1203 .unwrap_or_else(BorderRadius::zero); 1204 let clip_mode = if let Some(mode) = item["clip-mode"].as_str() { 1205 match mode { 1206 "outset" => BoxShadowClipMode::Outset, 1207 "inset" => BoxShadowClipMode::Inset, 1208 s => panic!("Unknown box shadow clip mode {}", s), 1209 } 1210 } else { 1211 BoxShadowClipMode::Outset 1212 }; 1213 1214 dl.push_box_shadow( 1215 info, 1216 box_bounds, 1217 offset, 1218 color, 1219 blur_radius, 1220 spread_radius, 1221 border_radius, 1222 clip_mode, 1223 ); 1224 } 1225 1226 fn handle_yuv_image( 1227 &mut self, 1228 dl: &mut DisplayListBuilder, 1229 wrench: &mut Wrench, 1230 item: &Yaml, 1231 info: &mut CommonItemProperties, 1232 ) { 1233 // TODO(gw): Support other YUV color depth and spaces. 1234 let color_depth = ColorDepth::Color8; 1235 let color_space = YuvColorSpace::Rec709; 1236 let color_range = ColorRange::Limited; 1237 1238 let yuv_data = match item["format"].as_str().expect("no format supplied") { 1239 "planar" => { 1240 let y_path = rsrc_path(&item["src-y"], &self.aux_dir); 1241 let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); 1242 1243 let u_path = rsrc_path(&item["src-u"], &self.aux_dir); 1244 let (u_key, _) = self.add_or_get_image(&u_path, None, item, wrench); 1245 1246 let v_path = rsrc_path(&item["src-v"], &self.aux_dir); 1247 let (v_key, _) = self.add_or_get_image(&v_path, None, item, wrench); 1248 1249 YuvData::PlanarYCbCr(y_key, u_key, v_key) 1250 } 1251 "nv12" => { 1252 let y_path = rsrc_path(&item["src-y"], &self.aux_dir); 1253 let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); 1254 1255 let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir); 1256 let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench); 1257 1258 YuvData::NV12(y_key, uv_key) 1259 } 1260 "p010" => { 1261 let y_path = rsrc_path(&item["src-y"], &self.aux_dir); 1262 let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); 1263 1264 let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir); 1265 let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench); 1266 1267 YuvData::P010(y_key, uv_key) 1268 } 1269 "nv16" => { 1270 let y_path = rsrc_path(&item["src-y"], &self.aux_dir); 1271 let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); 1272 1273 let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir); 1274 let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench); 1275 1276 YuvData::NV16(y_key, uv_key) 1277 } 1278 "interleaved" => { 1279 let yuv_path = rsrc_path(&item["src"], &self.aux_dir); 1280 let (yuv_key, _) = self.add_or_get_image(&yuv_path, None, item, wrench); 1281 1282 YuvData::InterleavedYCbCr(yuv_key) 1283 } 1284 _ => { 1285 panic!("unexpected yuv format"); 1286 } 1287 }; 1288 1289 let bounds = item["bounds"].as_vec_f32().unwrap(); 1290 let bounds = LayoutRect::from_origin_and_size( 1291 LayoutPoint::new(bounds[0], bounds[1]), 1292 LayoutSize::new(bounds[2], bounds[3]), 1293 ); 1294 1295 dl.push_yuv_image( 1296 info, 1297 bounds, 1298 yuv_data, 1299 color_depth, 1300 color_space, 1301 color_range, 1302 ImageRendering::Auto, 1303 ); 1304 } 1305 1306 fn handle_image( 1307 &mut self, 1308 dl: &mut DisplayListBuilder, 1309 wrench: &mut Wrench, 1310 item: &Yaml, 1311 info: &mut CommonItemProperties, 1312 ) { 1313 let filename = &item[if item["type"].is_badvalue() { 1314 "image" 1315 } else { 1316 "src" 1317 }]; 1318 let tiling = item["tile-size"].as_i64(); 1319 1320 let file = rsrc_path(filename, &self.aux_dir); 1321 let (image_key, image_dims) = 1322 self.add_or_get_image(&file, tiling, item, wrench); 1323 1324 let bounds_raws = item["bounds"].as_vec_f32().unwrap(); 1325 let bounds = if bounds_raws.len() == 2 { 1326 LayoutRect::from_origin_and_size(LayoutPoint::new(bounds_raws[0], bounds_raws[1]), image_dims) 1327 } else if bounds_raws.len() == 4 { 1328 LayoutRect::from_origin_and_size( 1329 LayoutPoint::new(bounds_raws[0], bounds_raws[1]), 1330 LayoutSize::new(bounds_raws[2], bounds_raws[3]), 1331 ) 1332 } else { 1333 panic!( 1334 "image expected 2 or 4 values in bounds, got '{:?}'", 1335 item["bounds"] 1336 ); 1337 }; 1338 let rendering = match item["rendering"].as_str() { 1339 Some("auto") | None => ImageRendering::Auto, 1340 Some("crisp-edges") => ImageRendering::CrispEdges, 1341 Some("pixelated") => ImageRendering::Pixelated, 1342 Some(_) => panic!( 1343 "ImageRendering can be auto, crisp-edges, or pixelated -- got {:?}", 1344 item 1345 ), 1346 }; 1347 let alpha_type = match item["alpha-type"].as_str() { 1348 Some("premultiplied-alpha") | None => AlphaType::PremultipliedAlpha, 1349 Some("alpha") => AlphaType::Alpha, 1350 Some(_) => panic!( 1351 "AlphaType can be premultiplied-alpha or alpha -- got {:?}", 1352 item 1353 ), 1354 }; 1355 let color = item["color"] 1356 .as_colorf() 1357 .unwrap_or_else(|| ColorF::WHITE); 1358 let stretch_size = item["stretch-size"].as_size(); 1359 let tile_spacing = item["tile-spacing"].as_size(); 1360 if stretch_size.is_none() && tile_spacing.is_none() { 1361 dl.push_image( 1362 info, 1363 bounds, 1364 rendering, 1365 alpha_type, 1366 image_key, 1367 color, 1368 ); 1369 } else { 1370 dl.push_repeating_image( 1371 info, 1372 bounds, 1373 stretch_size.unwrap_or(image_dims), 1374 tile_spacing.unwrap_or_else(LayoutSize::zero), 1375 rendering, 1376 alpha_type, 1377 image_key, 1378 color, 1379 ); 1380 } 1381 } 1382 1383 fn handle_text( 1384 &mut self, 1385 dl: &mut DisplayListBuilder, 1386 wrench: &mut Wrench, 1387 item: &Yaml, 1388 info: &mut CommonItemProperties, 1389 ) { 1390 let size = item["size"].as_pt_to_f32().unwrap_or(16.0); 1391 let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK); 1392 let synthetic_italics = if let Some(angle) = item["synthetic-italics"].as_f32() { 1393 SyntheticItalics::from_degrees(angle) 1394 } else if item["synthetic-italics"].as_bool().unwrap_or(false) { 1395 SyntheticItalics::enabled() 1396 } else { 1397 SyntheticItalics::disabled() 1398 }; 1399 1400 let mut flags = FontInstanceFlags::empty(); 1401 if item["synthetic-bold"].as_bool().unwrap_or(false) { 1402 flags |= FontInstanceFlags::SYNTHETIC_BOLD; 1403 } 1404 if item["embedded-bitmaps"].as_bool().unwrap_or(false) { 1405 flags |= FontInstanceFlags::EMBEDDED_BITMAPS; 1406 } 1407 if item["transpose"].as_bool().unwrap_or(false) { 1408 flags |= FontInstanceFlags::TRANSPOSE; 1409 } 1410 if item["flip-x"].as_bool().unwrap_or(false) { 1411 flags |= FontInstanceFlags::FLIP_X; 1412 } 1413 if item["flip-y"].as_bool().unwrap_or(false) { 1414 flags |= FontInstanceFlags::FLIP_Y; 1415 } 1416 1417 assert!( 1418 item["blur-radius"].is_badvalue(), 1419 "text no longer has a blur radius, use PushShadow and PopAllShadows" 1420 ); 1421 1422 let desc = FontDescriptor::from_yaml(item, &self.aux_dir); 1423 let font_key = self.get_or_create_font(desc, wrench); 1424 let font_instance_key = self.get_or_create_font_instance(font_key, 1425 size, 1426 flags, 1427 synthetic_italics, 1428 wrench); 1429 1430 assert!( 1431 !(item["glyphs"].is_badvalue() && item["text"].is_badvalue()), 1432 "text item had neither text nor glyphs!" 1433 ); 1434 1435 let (glyphs, rect) = if item["text"].is_badvalue() { 1436 // if glyphs are specified, then the glyph positions can have the 1437 // origin baked in. 1438 let origin = item["origin"] 1439 .as_point() 1440 .unwrap_or(LayoutPoint::new(0.0, 0.0)); 1441 let glyph_indices = item["glyphs"].as_vec_u32().unwrap(); 1442 let glyph_offsets = item["offsets"].as_vec_f32().unwrap(); 1443 assert_eq!(glyph_offsets.len(), glyph_indices.len() * 2); 1444 1445 let glyphs = glyph_indices 1446 .iter() 1447 .enumerate() 1448 .map(|k| { 1449 GlyphInstance { 1450 index: *k.1, 1451 // In the future we want to change the API to be relative, eliminating this 1452 point: LayoutPoint::new( 1453 origin.x + glyph_offsets[k.0 * 2], 1454 origin.y + glyph_offsets[k.0 * 2 + 1], 1455 ), 1456 } 1457 }) 1458 .collect::<Vec<_>>(); 1459 // TODO(gw): We could optionally use the WR API to query glyph dimensions 1460 // here and calculate the bounding region here if we want to. 1461 let rect = item["bounds"] 1462 .as_rect() 1463 .expect("Text items with glyphs require bounds [for now]"); 1464 (glyphs, rect) 1465 } else { 1466 let text = item["text"].as_str().unwrap(); 1467 let origin = item["origin"] 1468 .as_point() 1469 .expect("origin required for text without glyphs"); 1470 let (glyph_indices, glyph_positions, bounds) = wrench.layout_simple_ascii( 1471 font_key, 1472 font_instance_key, 1473 text, 1474 size, 1475 origin, 1476 flags, 1477 ); 1478 1479 let glyphs = glyph_indices 1480 .iter() 1481 .zip(glyph_positions) 1482 .map(|arg| { 1483 GlyphInstance { 1484 index: *arg.0 as u32, 1485 point: arg.1, 1486 } 1487 }) 1488 .collect::<Vec<_>>(); 1489 (glyphs, bounds) 1490 }; 1491 1492 dl.push_text( 1493 info, 1494 rect, 1495 &glyphs, 1496 font_instance_key, 1497 color, 1498 None, 1499 ); 1500 } 1501 1502 fn handle_iframe( 1503 &mut self, 1504 dl: &mut DisplayListBuilder, 1505 item: &Yaml, 1506 info: &mut CommonItemProperties, 1507 ) { 1508 let bounds = item["bounds"].as_rect().expect("iframe must have bounds"); 1509 let pipeline_id = item["id"].as_pipeline_id().unwrap(); 1510 let ignore = item["ignore_missing_pipeline"].as_bool().unwrap_or(true); 1511 dl.push_iframe( 1512 bounds, 1513 info.clip_rect, 1514 &SpaceAndClipInfo { 1515 spatial_id: info.spatial_id, 1516 clip_chain_id: info.clip_chain_id 1517 }, 1518 pipeline_id, 1519 ignore 1520 ); 1521 } 1522 1523 fn get_item_type_from_yaml(item: &Yaml) -> &str { 1524 let shorthands = [ 1525 "rect", 1526 "image", 1527 "text", 1528 "glyphs", 1529 "box-shadow", // Note: box_shadow shorthand check has to come before border. 1530 "border", 1531 "gradient", 1532 "radial-gradient", 1533 "conic-gradient" 1534 ]; 1535 1536 for shorthand in shorthands.iter() { 1537 if !item[*shorthand].is_badvalue() { 1538 return shorthand; 1539 } 1540 } 1541 item["type"].as_str().unwrap_or("unknown") 1542 } 1543 1544 fn add_display_list_items_from_yaml( 1545 &mut self, 1546 dl: &mut DisplayListBuilder, 1547 wrench: &mut Wrench, 1548 yaml_items: &[Yaml], 1549 ) { 1550 // A very large number (but safely far away from finite limits of f32) 1551 let big_number = 1.0e30; 1552 // A rect that should in practical terms serve as a no-op for clipping 1553 let full_clip = LayoutRect::from_origin_and_size( 1554 LayoutPoint::new(-big_number / 2.0, -big_number / 2.0), 1555 LayoutSize::new(big_number, big_number)); 1556 1557 for item in yaml_items { 1558 let item_type = Self::get_item_type_from_yaml(item); 1559 1560 let spatial_id = self.to_spatial_id(&item["spatial-id"], dl.pipeline_id); 1561 1562 if let Some(spatial_id) = spatial_id { 1563 self.spatial_id_stack.push(spatial_id); 1564 } 1565 1566 let clip_rect = item["clip-rect"].as_rect().unwrap_or(full_clip); 1567 let clip_chain_id = self.to_clip_chain_id(&item["clip-chain"], dl).unwrap_or(ClipChainId::INVALID); 1568 1569 let mut flags = PrimitiveFlags::default(); 1570 for (key, flag) in [ 1571 ("backface-visible", PrimitiveFlags::IS_BACKFACE_VISIBLE), 1572 ("scrollbar-container", PrimitiveFlags::IS_SCROLLBAR_CONTAINER), 1573 ("prefer-compositor-surface", PrimitiveFlags::PREFER_COMPOSITOR_SURFACE), 1574 ] { 1575 if let Some(value) = item[key].as_bool() { 1576 flags.set(flag, value); 1577 } 1578 } 1579 1580 1581 let mut info = CommonItemProperties { 1582 clip_rect, 1583 clip_chain_id, 1584 spatial_id: self.top_space(), 1585 flags, 1586 }; 1587 1588 match item_type { 1589 "rect" => self.handle_rect(dl, item, &info), 1590 "hit-test" => self.handle_hit_test(dl, item, &mut info), 1591 "line" => self.handle_line(dl, item, &mut info), 1592 "image" => self.handle_image(dl, wrench, item, &mut info), 1593 "yuv-image" => self.handle_yuv_image(dl, wrench, item, &mut info), 1594 "text" | "glyphs" => self.handle_text(dl, wrench, item, &mut info), 1595 "scroll-frame" => self.handle_scroll_frame(dl, wrench, item), 1596 "sticky-frame" => self.handle_sticky_frame(dl, wrench, item), 1597 "clip" => self.handle_clip(dl, wrench, item), 1598 "clip-chain" => self.handle_clip_chain(dl, item), 1599 "border" => self.handle_border(dl, wrench, item, &mut info), 1600 "gradient" => self.handle_gradient(dl, item, &mut info), 1601 "radial-gradient" => self.handle_radial_gradient(dl, item, &mut info), 1602 "conic-gradient" => self.handle_conic_gradient(dl, item, &mut info), 1603 "box-shadow" => self.handle_box_shadow(dl, item, &mut info), 1604 "iframe" => self.handle_iframe(dl, item, &mut info), 1605 "stacking-context" => { 1606 self.add_stacking_context_from_yaml(dl, wrench, item, IsRoot(false), &mut info) 1607 } 1608 "reference-frame" => self.handle_reference_frame(dl, wrench, item), 1609 "computed-frame" => self.handle_computed_frame(dl, wrench, item), 1610 "shadow" => self.handle_push_shadow(dl, item, &mut info), 1611 "pop-all-shadows" => self.handle_pop_all_shadows(dl), 1612 "backdrop-filter" => self.handle_backdrop_filter(dl, item, &mut info), 1613 _ => println!("Skipping unknown item type: {:?}", item), 1614 } 1615 1616 if spatial_id.is_some() { 1617 self.spatial_id_stack.pop().unwrap(); 1618 } 1619 } 1620 } 1621 1622 fn next_spatial_key(&mut self) -> SpatialTreeItemKey { 1623 let key = SpatialTreeItemKey::new(self.next_spatial_key, 0); 1624 self.next_spatial_key += 1; 1625 key 1626 } 1627 1628 fn handle_scroll_frame( 1629 &mut self, 1630 dl: &mut DisplayListBuilder, 1631 wrench: &mut Wrench, 1632 yaml: &Yaml, 1633 ) { 1634 let clip_rect = yaml["bounds"] 1635 .as_rect() 1636 .expect("scroll frame must have a bounds"); 1637 let content_size = yaml["content-size"].as_size().unwrap_or_else(|| clip_rect.size()); 1638 let content_rect = LayoutRect::from_origin_and_size(clip_rect.min, content_size); 1639 let external_scroll_offset = yaml["external-scroll-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero); 1640 let scroll_generation = yaml["scroll-generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64); 1641 let has_scroll_linked_effect = 1642 yaml["has-scroll-linked-effect"].as_bool().map_or(HasScrollLinkedEffect::default(), 1643 |v| if v { HasScrollLinkedEffect::Yes } else { HasScrollLinkedEffect::No } 1644 ); 1645 1646 let numeric_id = yaml["id"].as_i64().map(|id| id as u64); 1647 1648 let external_id = ExternalScrollId(self.next_external_scroll_id, dl.pipeline_id); 1649 self.next_external_scroll_id += 1; 1650 1651 if let Some(vector) = yaml["scroll-offset"].as_vector() { 1652 self.scroll_offsets.insert( 1653 external_id, 1654 vec![SampledScrollOffset { 1655 offset: vector, 1656 generation: APZScrollGeneration::default(), 1657 }], 1658 ); 1659 } 1660 1661 if !yaml["scroll-offsets"].is_badvalue() { 1662 let mut offsets = Vec::new(); 1663 for entry in yaml["scroll-offsets"].as_vec().unwrap() { 1664 let offset = entry["offset"].as_vector().unwrap_or(LayoutVector2D::zero()); 1665 let generation = entry["generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64); 1666 offsets.push(SampledScrollOffset { offset, generation }); 1667 } 1668 self.scroll_offsets.insert(external_id, offsets); 1669 } 1670 1671 let clip_to_frame = yaml["clip-to-frame"].as_bool().unwrap_or(false); 1672 1673 let clip_id = if clip_to_frame { 1674 Some(dl.define_clip_rect( 1675 self.top_space(), 1676 clip_rect, 1677 )) 1678 } else { 1679 None 1680 }; 1681 1682 let spatial_id = dl.define_scroll_frame( 1683 self.top_space(), 1684 external_id, 1685 content_rect, 1686 clip_rect, 1687 external_scroll_offset, 1688 scroll_generation, 1689 has_scroll_linked_effect, 1690 self.next_spatial_key(), 1691 ); 1692 if let Some(numeric_id) = numeric_id { 1693 self.add_spatial_id_mapping(numeric_id, spatial_id); 1694 if let Some(clip_id) = clip_id { 1695 self.add_clip_id_mapping(numeric_id, clip_id); 1696 } 1697 } 1698 1699 if let Some(yaml_items) = yaml["items"].as_vec() { 1700 self.spatial_id_stack.push(spatial_id); 1701 self.add_display_list_items_from_yaml(dl, wrench, yaml_items); 1702 self.spatial_id_stack.pop().unwrap(); 1703 } 1704 } 1705 1706 fn handle_sticky_frame( 1707 &mut self, 1708 dl: &mut DisplayListBuilder, 1709 wrench: &mut Wrench, 1710 yaml: &Yaml, 1711 ) { 1712 let bounds = yaml["bounds"].as_rect().expect("sticky frame must have a bounds"); 1713 let numeric_id = yaml["id"].as_i64().map(|id| id as u64); 1714 1715 let real_id = dl.define_sticky_frame( 1716 *self.spatial_id_stack.last().unwrap(), 1717 bounds, 1718 SideOffsets2D::new( 1719 yaml["margin-top"].as_f32(), 1720 yaml["margin-right"].as_f32(), 1721 yaml["margin-bottom"].as_f32(), 1722 yaml["margin-left"].as_f32(), 1723 ), 1724 yaml["vertical-offset-bounds"].as_sticky_offset_bounds(), 1725 yaml["horizontal-offset-bounds"].as_sticky_offset_bounds(), 1726 yaml["previously-applied-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero), 1727 self.next_spatial_key(), 1728 None, 1729 ); 1730 1731 if let Some(numeric_id) = numeric_id { 1732 self.add_spatial_id_mapping(numeric_id, real_id); 1733 } 1734 1735 if let Some(yaml_items) = yaml["items"].as_vec() { 1736 self.spatial_id_stack.push(real_id); 1737 self.add_display_list_items_from_yaml(dl, wrench, yaml_items); 1738 self.spatial_id_stack.pop().unwrap(); 1739 } 1740 } 1741 1742 fn resolve_binding<'a>( 1743 &'a self, 1744 yaml: &'a Yaml, 1745 ) -> &'a Yaml { 1746 if let Some(keyframes) = &self.keyframes { 1747 if let Some(s) = yaml.as_str() { 1748 const PREFIX: &str = "key("; 1749 const SUFFIX: &str = ")"; 1750 if let Some(key) = s.strip_prefix(PREFIX).and_then(|s| s.strip_suffix(SUFFIX)) { 1751 return &keyframes[key][self.requested_frame]; 1752 } 1753 } 1754 } 1755 1756 yaml 1757 } 1758 1759 fn resolve_colorf( 1760 &self, 1761 yaml: &Yaml, 1762 ) -> Option<ColorF> { 1763 self.resolve_binding(yaml) 1764 .as_colorf() 1765 } 1766 1767 fn resolve_rect( 1768 &self, 1769 yaml: &Yaml, 1770 ) -> LayoutRect { 1771 self.resolve_binding(yaml) 1772 .as_rect() 1773 .unwrap_or_else(|| panic!("invalid rect {:?}", yaml)) 1774 } 1775 1776 fn resolve_vector( 1777 &self, 1778 yaml: &Yaml, 1779 default: LayoutVector2D, 1780 ) -> LayoutVector2D { 1781 self.resolve_binding(yaml) 1782 .as_vector() 1783 .unwrap_or(default) 1784 } 1785 1786 fn handle_push_shadow( 1787 &mut self, 1788 dl: &mut DisplayListBuilder, 1789 yaml: &Yaml, 1790 info: &mut CommonItemProperties, 1791 ) { 1792 let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0); 1793 let offset = yaml["offset"].as_vector().unwrap_or_else(LayoutVector2D::zero); 1794 let color = yaml["color"].as_colorf().unwrap_or(ColorF::BLACK); 1795 1796 dl.push_shadow( 1797 &SpaceAndClipInfo { spatial_id: info.spatial_id, clip_chain_id: info.clip_chain_id }, 1798 Shadow { 1799 blur_radius, 1800 offset, 1801 color, 1802 }, 1803 true, 1804 ); 1805 } 1806 1807 fn handle_pop_all_shadows(&mut self, dl: &mut DisplayListBuilder) { 1808 dl.pop_all_shadows(); 1809 } 1810 1811 fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) { 1812 let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id"); 1813 let clip_ids: Vec<ClipId> = yaml["clips"] 1814 .as_vec_u64() 1815 .unwrap_or_default() 1816 .iter() 1817 .map(|id| self.user_clip_id_map[id]) 1818 .collect(); 1819 1820 let parent = self.to_clip_chain_id(&yaml["parent"], builder); 1821 let real_id = builder.define_clip_chain(parent, clip_ids); 1822 self.add_clip_chain_id_mapping(numeric_id as u64, real_id); 1823 } 1824 1825 fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) { 1826 let numeric_id = yaml["id"].as_i64(); 1827 let spatial_id = self.top_space(); 1828 let complex_clips = yaml["complex"].as_complex_clip_regions(); 1829 let mut clip_id = None; 1830 1831 if let Some(clip_rect) = yaml["bounds"].as_rect() { 1832 clip_id = Some(dl.define_clip_rect( 1833 spatial_id, 1834 clip_rect, 1835 )); 1836 } 1837 1838 if let Some(image_mask) = self.as_image_mask(&yaml["image-mask"], wrench) { 1839 assert!(clip_id.is_none(), "invalid clip definition"); 1840 1841 clip_id = Some(dl.define_clip_image_mask( 1842 spatial_id, 1843 image_mask, 1844 &[], 1845 FillRule::Nonzero, 1846 )); 1847 } 1848 1849 if !complex_clips.is_empty() { 1850 // Only 1 complex clip is supported per clip (todo: change yaml format) 1851 assert_eq!(complex_clips.len(), 1); 1852 assert!(clip_id.is_none(), "invalid clip definition"); 1853 1854 clip_id = Some(dl.define_clip_rounded_rect( 1855 spatial_id, 1856 complex_clips[0], 1857 )); 1858 } 1859 1860 if let Some(clip_id) = clip_id { 1861 if let Some(numeric_id) = numeric_id { 1862 self.add_clip_id_mapping(numeric_id as u64, clip_id); 1863 } 1864 } 1865 } 1866 1867 fn push_reference_frame( 1868 &mut self, 1869 dl: &mut DisplayListBuilder, 1870 default_bounds: impl Fn() -> LayoutRect, 1871 yaml: &Yaml, 1872 ) -> SpatialId { 1873 let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); 1874 let default_transform_origin = LayoutPoint::new( 1875 bounds.min.x + bounds.width() * 0.5, 1876 bounds.min.y + bounds.height() * 0.5, 1877 ); 1878 1879 let transform_style = yaml["transform-style"] 1880 .as_transform_style() 1881 .unwrap_or(TransformStyle::Flat); 1882 1883 let transform_origin = yaml["transform-origin"] 1884 .as_point() 1885 .unwrap_or(default_transform_origin); 1886 1887 assert!( 1888 yaml["transform"].is_badvalue() || 1889 yaml["perspective"].is_badvalue(), 1890 "Should have one of either transform or perspective" 1891 ); 1892 1893 let perspective_origin = yaml["perspective-origin"] 1894 .as_point() 1895 .unwrap_or(default_transform_origin); 1896 1897 let is_2d = yaml["is-2d"].as_bool().unwrap_or(false); 1898 let should_snap = yaml["should-snap"].as_bool().unwrap_or(false); 1899 1900 let reference_frame_kind = if !yaml["perspective"].is_badvalue() { 1901 ReferenceFrameKind::Perspective { scrolling_relative_to: None } 1902 } else { 1903 ReferenceFrameKind::Transform { 1904 is_2d_scale_translation: is_2d, 1905 should_snap, 1906 paired_with_perspective: yaml["paired-with-perspective"].as_bool().unwrap_or(false), 1907 } 1908 }; 1909 1910 let transform = yaml["transform"] 1911 .as_transform(&transform_origin); 1912 1913 let perspective = match yaml["perspective"].as_f32() { 1914 Some(value) if value != 0.0 => { 1915 Some(make_perspective(perspective_origin, value as f32)) 1916 } 1917 Some(..) => None, 1918 _ => yaml["perspective"].as_matrix4d(), 1919 }; 1920 1921 let reference_frame_id = dl.push_reference_frame( 1922 bounds.min, 1923 *self.spatial_id_stack.last().unwrap(), 1924 transform_style, 1925 transform.or(perspective).unwrap_or_default().into(), 1926 reference_frame_kind, 1927 self.next_spatial_key(), 1928 ); 1929 1930 let numeric_id = yaml["id"].as_i64(); 1931 if let Some(numeric_id) = numeric_id { 1932 self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id); 1933 } 1934 1935 reference_frame_id 1936 } 1937 1938 fn handle_reference_frame( 1939 &mut self, 1940 dl: &mut DisplayListBuilder, 1941 wrench: &mut Wrench, 1942 yaml: &Yaml, 1943 ) { 1944 let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); 1945 let real_id = self.push_reference_frame(dl, default_bounds, yaml); 1946 self.spatial_id_stack.push(real_id); 1947 1948 if let Some(yaml_items) = yaml["items"].as_vec() { 1949 self.add_display_list_items_from_yaml(dl, wrench, yaml_items); 1950 } 1951 1952 self.spatial_id_stack.pop().unwrap(); 1953 dl.pop_reference_frame(); 1954 } 1955 1956 fn push_computed_frame( 1957 &mut self, 1958 dl: &mut DisplayListBuilder, 1959 default_bounds: impl Fn() -> LayoutRect, 1960 yaml: &Yaml, 1961 ) -> SpatialId { 1962 let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); 1963 1964 let scale_from = yaml["scale-from"].as_size(); 1965 let vertical_flip = yaml["vertical-flip"].as_bool().unwrap_or(false); 1966 let rotation = yaml["rotation"].as_rotation().unwrap_or(Rotation::Degree0); 1967 1968 let reference_frame_id = dl.push_computed_frame( 1969 bounds.min, 1970 *self.spatial_id_stack.last().unwrap(), 1971 scale_from, 1972 vertical_flip, 1973 rotation, 1974 self.next_spatial_key(), 1975 ); 1976 1977 let numeric_id = yaml["id"].as_i64(); 1978 if let Some(numeric_id) = numeric_id { 1979 self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id); 1980 } 1981 1982 reference_frame_id 1983 } 1984 1985 fn handle_computed_frame( 1986 &mut self, 1987 dl: &mut DisplayListBuilder, 1988 wrench: &mut Wrench, 1989 yaml: &Yaml, 1990 ) { 1991 let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); 1992 let real_id = self.push_computed_frame(dl, default_bounds, yaml); 1993 self.spatial_id_stack.push(real_id); 1994 1995 if let Some(yaml_items) = yaml["items"].as_vec() { 1996 self.add_display_list_items_from_yaml(dl, wrench, yaml_items); 1997 } 1998 1999 self.spatial_id_stack.pop().unwrap(); 2000 dl.pop_reference_frame(); 2001 } 2002 2003 fn add_stacking_context_from_yaml( 2004 &mut self, 2005 dl: &mut DisplayListBuilder, 2006 wrench: &mut Wrench, 2007 yaml: &Yaml, 2008 IsRoot(is_root): IsRoot, 2009 info: &mut CommonItemProperties, 2010 ) { 2011 let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); 2012 let mut bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); 2013 2014 let pushed_reference_frame = 2015 if !yaml["transform"].is_badvalue() || !yaml["perspective"].is_badvalue() { 2016 let reference_frame_id = self.push_reference_frame(dl, default_bounds, yaml); 2017 self.spatial_id_stack.push(reference_frame_id); 2018 bounds.max -= bounds.min.to_vector(); 2019 bounds.min = LayoutPoint::zero(); 2020 true 2021 } else { 2022 false 2023 }; 2024 2025 let clip_chain_id = self.to_clip_chain_id(&yaml["clip-chain"], dl); 2026 2027 let transform_style = yaml["transform-style"] 2028 .as_transform_style() 2029 .unwrap_or(TransformStyle::Flat); 2030 let mix_blend_mode = yaml["mix-blend-mode"] 2031 .as_mix_blend_mode() 2032 .unwrap_or(MixBlendMode::Normal); 2033 let raster_space = yaml["raster-space"] 2034 .as_raster_space() 2035 .unwrap_or(RasterSpace::Screen); 2036 let is_blend_container = yaml["blend-container"].as_bool().unwrap_or(false); 2037 let wraps_backdrop_filter = yaml["wraps-backdrop-filter"].as_bool().unwrap_or(false); 2038 2039 if is_root { 2040 if let Some(vector) = yaml["scroll-offset"].as_vector() { 2041 let external_id = ExternalScrollId(0, dl.pipeline_id); 2042 self.scroll_offsets.insert( 2043 external_id, 2044 vec![SampledScrollOffset { 2045 offset: vector, 2046 generation: APZScrollGeneration::default(), 2047 }], 2048 ); 2049 } 2050 } 2051 2052 let filters = yaml["filters"].as_vec_filter_op().unwrap_or_default(); 2053 let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or_default(); 2054 2055 let snapshot = if !yaml["snapshot"].is_badvalue() { 2056 let yaml = &yaml["snapshot"]; 2057 let name = yaml["name"].as_str().unwrap_or("snapshot"); 2058 let area = yaml["area"].as_rect().unwrap_or(bounds); 2059 let detached = yaml["detached"].as_bool().unwrap_or(false); 2060 2061 let key = SnapshotImageKey(wrench.api.generate_image_key()); 2062 self.snapshots.insert(name.to_string(), Snapshot { 2063 key, 2064 size: bounds.size(), 2065 }); 2066 2067 let mut txn = Transaction::new(); 2068 txn.add_snapshot_image(key); 2069 wrench.api.send_transaction(wrench.document_id, txn); 2070 2071 Some(SnapshotInfo { key, area, detached }) 2072 } else { 2073 None 2074 }; 2075 2076 let mut flags = StackingContextFlags::empty(); 2077 flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container); 2078 flags.set(StackingContextFlags::WRAPS_BACKDROP_FILTER, wraps_backdrop_filter); 2079 2080 dl.push_stacking_context( 2081 bounds.min, 2082 *self.spatial_id_stack.last().unwrap(), 2083 info.flags, 2084 clip_chain_id, 2085 transform_style, 2086 mix_blend_mode, 2087 &filters, 2088 &filter_datas, 2089 raster_space, 2090 flags, 2091 snapshot, 2092 ); 2093 2094 if let Some(yaml_items) = yaml["items"].as_vec() { 2095 self.add_display_list_items_from_yaml(dl, wrench, yaml_items); 2096 } 2097 2098 dl.pop_stacking_context(); 2099 2100 if pushed_reference_frame { 2101 self.spatial_id_stack.pop().unwrap(); 2102 dl.pop_reference_frame(); 2103 } 2104 } 2105 2106 fn handle_backdrop_filter( 2107 &mut self, 2108 dl: &mut DisplayListBuilder, 2109 item: &Yaml, 2110 info: &mut CommonItemProperties, 2111 ) { 2112 info.clip_rect = try_intersect!( 2113 self.resolve_rect(&item["bounds"]), 2114 &info.clip_rect 2115 ); 2116 2117 let filters = item["filters"].as_vec_filter_op().unwrap_or_default(); 2118 let filter_datas = item["filter-datas"].as_vec_filter_data().unwrap_or_default(); 2119 2120 dl.push_backdrop_filter( 2121 info, 2122 &filters, 2123 &filter_datas, 2124 ); 2125 } 2126 } 2127 2128 impl WrenchThing for YamlFrameReader { 2129 fn do_frame(&mut self, wrench: &mut Wrench) -> u32 { 2130 let mut should_build_yaml = false; 2131 2132 // If YAML isn't read yet, or watching source file, reload from disk. 2133 if self.yaml_string.is_empty() || self.watch_source { 2134 self.yaml_string = std::fs::read_to_string(&self.yaml_path) 2135 .unwrap_or_else(|_| panic!("YAML '{:?}' doesn't exist", self.yaml_path)); 2136 should_build_yaml = true; 2137 } 2138 2139 // Evaluate conditions that require parsing the YAML. 2140 if self.built_frame != self.requested_frame { 2141 // Requested frame has changed 2142 should_build_yaml = true; 2143 } 2144 2145 // Build the DL from YAML if required 2146 if should_build_yaml { 2147 self.build(wrench); 2148 } 2149 2150 // Determine whether to send a new DL, or just refresh. 2151 if should_build_yaml || wrench.should_rebuild_display_lists() { 2152 wrench.begin_frame(); 2153 wrench.send_lists( 2154 &mut self.frame_count, 2155 self.display_lists.clone(), 2156 &self.scroll_offsets, 2157 ); 2158 } else { 2159 wrench.refresh(); 2160 } 2161 2162 self.frame_count 2163 } 2164 2165 fn next_frame(&mut self) { 2166 let mut max_frame_count = 0; 2167 if let Some(ref keyframes) = self.keyframes { 2168 for (_, values) in keyframes.as_hash().unwrap() { 2169 max_frame_count = max_frame_count.max(values.as_vec().unwrap().len()); 2170 } 2171 } 2172 if self.requested_frame + 1 < max_frame_count { 2173 self.requested_frame += 1; 2174 } 2175 } 2176 2177 fn prev_frame(&mut self) { 2178 if self.requested_frame > 0 { 2179 self.requested_frame -= 1; 2180 } 2181 } 2182 }