/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use euclid::SideOffsets2D; use gleam::gl; use image::GenericImageView; use crate::parse_function::parse_function; use crate::premultiply::premultiply; use std::collections::HashMap; use std::convert::TryInto; use std::fs::File; use std::io::Read; use std::path::{Path, PathBuf}; use std::usize; use webrender::api::*; use webrender::render_api::*; use webrender::api::units::*; use webrender::api::FillRule; use crate::wrench::{FontDescriptor, Wrench, WrenchThing}; use crate::yaml_helper::{StringEnum, YamlHelper, make_perspective}; use yaml_rust::{Yaml, YamlLoader}; use crate::PLATFORM_DEFAULT_FACE_NAME; macro_rules! try_intersect { ($first: expr, $second: expr) => { if let Some(rect) = ($first).intersection($second) { rect } else { warn!("skipping item with non-intersecting bounds and clip_rect"); return; } } } fn rsrc_path(item: &Yaml, aux_dir: &Path) -> PathBuf { let filename = item.as_str().unwrap(); let mut file = aux_dir.to_path_buf(); file.push(filename); file } impl FontDescriptor { fn from_yaml(item: &Yaml, aux_dir: &Path) -> FontDescriptor { if !item["family"].is_badvalue() { FontDescriptor::Properties { family: item["family"].as_str().unwrap().to_owned(), weight: item["weight"].as_i64().unwrap_or(400) as u32, style: item["style"].as_i64().unwrap_or(0) as u32, stretch: item["stretch"].as_i64().unwrap_or(5) as u32, } } else if !item["font"].is_badvalue() { let path = rsrc_path(&item["font"], aux_dir); FontDescriptor::Path { path, font_index: item["font-index"].as_i64().unwrap_or(0) as u32, } } else { FontDescriptor::Family { name: PLATFORM_DEFAULT_FACE_NAME.to_string(), } } } } struct LocalExternalImageHandler { texture_ids: Vec<(gl::GLuint, ImageDescriptor)>, } impl LocalExternalImageHandler { pub fn new() -> LocalExternalImageHandler { LocalExternalImageHandler { texture_ids: Vec::new(), } } fn init_gl_texture( id: gl::GLuint, gl_target: gl::GLuint, format_desc: webrender::FormatDesc, width: gl::GLint, height: gl::GLint, bytes: &[u8], gl: &dyn gl::Gl, ) { gl.bind_texture(gl_target, id); gl.tex_parameter_i(gl_target, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint); gl.tex_parameter_i(gl_target, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint); gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); gl.tex_image_2d( gl_target, 0, format_desc.internal as gl::GLint, width, height, 0, format_desc.external, format_desc.pixel_type, Some(bytes), ); gl.bind_texture(gl_target, 0); } pub fn add_image(&mut self, device: &webrender::Device, desc: ImageDescriptor, target: ImageBufferKind, image_data: ImageData, ) -> ImageData { let (image_id, channel_idx) = match image_data { ImageData::Raw(ref data) => { let gl = device.gl(); let texture_ids = gl.gen_textures(1); let format_desc = if desc.format == ImageFormat::BGRA8 { // Force BGRA8 data to RGBA8 layout to avoid potential // need for usage of texture-swizzle. webrender::FormatDesc { external: gl::BGRA, .. device.gl_describe_format(ImageFormat::RGBA8) } } else { device.gl_describe_format(desc.format) }; LocalExternalImageHandler::init_gl_texture( texture_ids[0], webrender::get_gl_target(target), format_desc, desc.size.width as gl::GLint, desc.size.height as gl::GLint, data, gl, ); self.texture_ids.push((texture_ids[0], desc)); (ExternalImageId((self.texture_ids.len() - 1) as u64), 0) }, _ => panic!("unsupported!"), }; ImageData::External( ExternalImageData { id: image_id, channel_index: channel_idx, image_type: ExternalImageType::TextureHandle(target) } ) } } impl ExternalImageHandler for LocalExternalImageHandler { fn lock( &mut self, key: ExternalImageId, _channel_index: u8, ) -> ExternalImage { let (id, desc) = self.texture_ids[key.0 as usize]; ExternalImage { uv: TexelRect::new(0.0, 0.0, desc.size.width as f32, desc.size.height as f32), source: ExternalImageSource::NativeTexture(id), } } fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {} } fn broadcast(base_vals: &[T], num_items: usize) -> Vec { if base_vals.len() == num_items { return base_vals.to_vec(); } assert_eq!( num_items % base_vals.len(), 0, "Cannot broadcast {} elements into {}", base_vals.len(), num_items ); let mut vals = vec![]; loop { if vals.len() == num_items { break; } vals.extend_from_slice(base_vals); } vals } enum CheckerboardKind { BlackGrey, BlackTransparent, } fn generate_checkerboard_image( border: u32, tile_x_size: u32, tile_y_size: u32, tile_x_count: u32, tile_y_count: u32, kind: CheckerboardKind, ) -> (ImageDescriptor, ImageData) { let width = 2 * border + tile_x_size * tile_x_count; let height = 2 * border + tile_y_size * tile_y_count; let mut pixels = Vec::new(); for y in 0 .. height { for x in 0 .. width { if y < border || y >= (height - border) || x < border || x >= (width - border) { pixels.push(0); pixels.push(0); pixels.push(0xff); pixels.push(0xff); } else { let xon = ((x - border) % (2 * tile_x_size)) < tile_x_size; let yon = ((y - border) % (2 * tile_y_size)) < tile_y_size; match kind { CheckerboardKind::BlackGrey => { let value = if xon ^ yon { 0xff } else { 0x7f }; pixels.push(value); pixels.push(value); pixels.push(value); pixels.push(0xff); } CheckerboardKind::BlackTransparent => { let value = if xon ^ yon { 0xff } else { 0x00 }; pixels.push(value); pixels.push(value); pixels.push(value); pixels.push(value); } } } } } let flags = match kind { CheckerboardKind::BlackGrey => ImageDescriptorFlags::IS_OPAQUE, CheckerboardKind::BlackTransparent => ImageDescriptorFlags::empty(), }; ( ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, flags), ImageData::new(pixels), ) } fn generate_xy_gradient_image(w: u32, h: u32) -> (ImageDescriptor, ImageData) { let mut pixels = Vec::with_capacity((w * h * 4) as usize); for y in 0 .. h { for x in 0 .. w { let grid = if x % 100 < 3 || y % 100 < 3 { 0.9 } else { 1.0 }; pixels.push((y as f32 / h as f32 * 255.0 * grid) as u8); pixels.push(0); pixels.push((x as f32 / w as f32 * 255.0 * grid) as u8); pixels.push(255); } } ( ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE), ImageData::new(pixels), ) } fn generate_solid_color_image( r: u8, g: u8, b: u8, a: u8, w: u32, h: u32, ) -> (ImageDescriptor, ImageData) { let num_pixels: usize = (w * h).try_into().unwrap(); let pixels = [b, g, r, a].repeat(num_pixels); let mut flags = ImageDescriptorFlags::empty(); if a == 255 { flags |= ImageDescriptorFlags::IS_OPAQUE; } ( ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, flags), ImageData::new(pixels), ) } fn is_image_opaque(format: ImageFormat, bytes: &[u8]) -> bool { match format { ImageFormat::BGRA8 | ImageFormat::RGBA8 => { let mut is_opaque = true; for i in 0 .. (bytes.len() / 4) { if bytes[i * 4 + 3] != 255 { is_opaque = false; break; } } is_opaque } ImageFormat::RG8 => true, ImageFormat::RG16 => true, ImageFormat::R8 => false, ImageFormat::R16 => false, ImageFormat::RGBAF32 | ImageFormat::RGBAI32 => unreachable!(), } } struct IsRoot(bool); pub struct YamlFrameReader { yaml_path: PathBuf, aux_dir: PathBuf, frame_count: u32, display_lists: Vec<(PipelineId, BuiltDisplayList)>, watch_source: bool, list_resources: bool, /// A HashMap of offsets which specify what scroll offsets particular /// scroll layers should be initialized with. scroll_offsets: HashMap>, next_external_scroll_id: u64, image_map: HashMap<(PathBuf, Option), (ImageKey, LayoutSize)>, fonts: HashMap, font_instances: HashMap<(FontKey, FontSize, FontInstanceFlags, Option, SyntheticItalics), FontInstanceKey>, font_render_mode: Option, allow_mipmaps: bool, /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML /// and having each of those ids correspond to a unique ClipId. user_clip_id_map: HashMap, user_clipchain_id_map: HashMap, user_spatial_id_map: HashMap, spatial_id_stack: Vec, requested_frame: usize, built_frame: usize, yaml_string: String, keyframes: Option, external_image_handler: Option>, next_spatial_key: u64, } impl YamlFrameReader { pub fn new(yaml_path: &Path) -> YamlFrameReader { YamlFrameReader { watch_source: false, list_resources: false, yaml_path: yaml_path.to_owned(), aux_dir: yaml_path.parent().unwrap().to_owned(), frame_count: 0, display_lists: Vec::new(), scroll_offsets: HashMap::new(), fonts: HashMap::new(), font_instances: HashMap::new(), font_render_mode: None, allow_mipmaps: false, image_map: HashMap::new(), user_clip_id_map: HashMap::new(), user_clipchain_id_map: HashMap::new(), user_spatial_id_map: HashMap::new(), spatial_id_stack: Vec::new(), yaml_string: String::new(), requested_frame: 0, built_frame: usize::MAX, keyframes: None, external_image_handler: Some(Box::new(LocalExternalImageHandler::new())), next_external_scroll_id: 1000, // arbitrary to easily see in logs which are implicit next_spatial_key: 0, } } pub fn deinit(mut self, wrench: &mut Wrench) { let mut txn = Transaction::new(); for (_, font_instance) in self.font_instances.drain() { txn.delete_font_instance(font_instance); } for (_, font) in self.fonts.drain() { txn.delete_font(font); } wrench.api.send_transaction(wrench.document_id, txn); } fn top_space(&self) -> SpatialId { *self.spatial_id_stack.last().unwrap() } pub fn yaml_path(&self) -> &PathBuf { &self.yaml_path } pub fn new_from_args(args: &clap::ArgMatches) -> YamlFrameReader { let yaml_file = args.value_of("INPUT").map(PathBuf::from).unwrap(); let mut y = YamlFrameReader::new(&yaml_file); y.keyframes = args.value_of("keyframes").map(|path| { let mut file = File::open(&path).unwrap(); let mut keyframes_string = String::new(); file.read_to_string(&mut keyframes_string).unwrap(); YamlLoader::load_from_str(&keyframes_string) .expect("Failed to parse keyframes file") .pop() .unwrap() }); y.list_resources = args.is_present("list-resources"); y.watch_source = args.is_present("watch"); y } pub fn reset(&mut self) { self.scroll_offsets.clear(); self.display_lists.clear(); } fn build(&mut self, wrench: &mut Wrench) { let yaml = YamlLoader::load_from_str(&self.yaml_string) .map(|mut yaml| { assert_eq!(yaml.len(), 1); yaml.pop().unwrap() }) .expect("Failed to parse YAML file"); self.reset(); if let Some(pipelines) = yaml["pipelines"].as_vec() { for pipeline in pipelines { self.build_pipeline(wrench, pipeline["id"].as_pipeline_id().unwrap(), pipeline); } } let root_stacking_context = &yaml["root"]; assert_ne!(*root_stacking_context, Yaml::BadValue); self.build_pipeline(wrench, wrench.root_pipeline_id, root_stacking_context); // If replaying the same frame during interactive use, the frame gets rebuilt, // but the external image handler has already been consumed by the renderer. if let Some(external_image_handler) = self.external_image_handler.take() { wrench.renderer.set_external_image_handler(external_image_handler); } } fn build_pipeline( &mut self, wrench: &mut Wrench, pipeline_id: PipelineId, yaml: &Yaml ) { // Don't allow referencing clips between pipelines for now. self.user_clip_id_map.clear(); self.user_clipchain_id_map.clear(); self.user_spatial_id_map.clear(); self.spatial_id_stack.clear(); self.spatial_id_stack.push(SpatialId::root_scroll_node(pipeline_id)); let mut builder = DisplayListBuilder::new(pipeline_id); builder.begin(); let mut info = CommonItemProperties { clip_rect: LayoutRect::zero(), clip_chain_id: ClipChainId::INVALID, spatial_id: SpatialId::new(0, PipelineId::dummy()), flags: PrimitiveFlags::default(), }; self.add_stacking_context_from_yaml(&mut builder, wrench, yaml, IsRoot(true), &mut info); self.display_lists.push(builder.end()); assert_eq!(self.spatial_id_stack.len(), 1); } fn to_clip_chain_id( &self, item: &Yaml, builder: &mut DisplayListBuilder, ) -> Option { match *item { Yaml::Integer(value) => { Some(self.user_clipchain_id_map[&(value as u64)]) } Yaml::Array(ref array) => { let clip_ids: Vec = array .iter() .map(|id| { let id = id.as_i64().expect("invalid clip id") as u64; self.user_clip_id_map[&id] }) .collect(); Some(builder.define_clip_chain(None, clip_ids)) } _ => None, } } fn to_spatial_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option { match *item { Yaml::Integer(value) => Some(self.user_spatial_id_map[&(value as u64)]), Yaml::String(ref id_string) if id_string == "root-reference-frame" => Some(SpatialId::root_reference_frame(pipeline_id)), Yaml::String(ref id_string) if id_string == "root-scroll-node" => Some(SpatialId::root_scroll_node(pipeline_id)), Yaml::BadValue => None, _ => { println!("Unable to parse SpatialId {:?}", item); None } } } fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) { assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip"); self.user_clip_id_map.insert(numeric_id, real_id); } fn add_clip_chain_id_mapping(&mut self, numeric_id: u64, real_id: ClipChainId) { assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip-chain"); self.user_clipchain_id_map.insert(numeric_id, real_id); } fn add_spatial_id_mapping(&mut self, numeric_id: u64, real_id: SpatialId) { assert_ne!(numeric_id, 0, "id=0 is reserved for the root reference frame"); assert_ne!(numeric_id, 1, "id=1 is reserved for the root scroll node"); self.user_spatial_id_map.insert(numeric_id, real_id); } fn to_hit_testing_tag(&self, item: &Yaml) -> Option { match *item { Yaml::Array(ref array) if array.len() == 2 => { match (array[0].as_i64(), array[1].as_i64()) { (Some(first), Some(second)) => Some((first as u64, second as u16)), _ => None, } } _ => None, } } fn add_or_get_image( &mut self, file: &Path, tiling: Option, item: &Yaml, wrench: &mut Wrench, ) -> (ImageKey, LayoutSize) { let key = (file.to_owned(), tiling); if let Some(k) = self.image_map.get(&key) { return *k; } if self.list_resources { println!("{}", file.to_string_lossy()); } let (descriptor, image_data) = match image::open(file) { Ok(image) => { let (image_width, image_height) = image.dimensions(); let (format, bytes) = match image { image::DynamicImage::ImageLuma8(_) => { (ImageFormat::R8, image.to_bytes()) } image::DynamicImage::ImageRgba8(_) => { let mut pixels = image.to_bytes(); premultiply(pixels.as_mut_slice()); (ImageFormat::BGRA8, pixels) } image::DynamicImage::ImageRgb8(_) => { let bytes = image.to_bytes(); let mut pixels = Vec::with_capacity(image_width as usize * image_height as usize * 4); for bgr in bytes.chunks(3) { pixels.extend_from_slice(&[ bgr[2], bgr[1], bgr[0], 0xff ]); } (ImageFormat::BGRA8, pixels) } _ => panic!("We don't support whatever your crazy image type is, come on"), }; let mut flags = ImageDescriptorFlags::empty(); if is_image_opaque(format, &bytes[..]) { flags |= ImageDescriptorFlags::IS_OPAQUE; } if self.allow_mipmaps { flags |= ImageDescriptorFlags::ALLOW_MIPMAPS; } let descriptor = ImageDescriptor::new( image_width as i32, image_height as i32, format, flags, ); let data = ImageData::new(bytes); (descriptor, data) } _ => { // This is a hack but it is convenient when generating test cases and avoids // bloating the repository. match parse_function( file.components() .last() .unwrap() .as_os_str() .to_str() .unwrap(), ) { ("xy-gradient", args, _) => generate_xy_gradient_image( args.get(0).unwrap_or(&"1000").parse::().unwrap(), args.get(1).unwrap_or(&"1000").parse::().unwrap(), ), ("solid-color", args, _) => generate_solid_color_image( args.get(0).unwrap_or(&"255").parse::().unwrap(), args.get(1).unwrap_or(&"255").parse::().unwrap(), args.get(2).unwrap_or(&"255").parse::().unwrap(), args.get(3).unwrap_or(&"255").parse::().unwrap(), args.get(4).unwrap_or(&"1000").parse::().unwrap(), args.get(5).unwrap_or(&"1000").parse::().unwrap(), ), (name @ "transparent-checkerboard", args, _) | (name @ "checkerboard", args, _) => { let border = args.get(0).unwrap_or(&"4").parse::().unwrap(); let (x_size, y_size, x_count, y_count) = match args.len() { 3 => { let size = args.get(1).unwrap_or(&"32").parse::().unwrap(); let count = args.get(2).unwrap_or(&"8").parse::().unwrap(); (size, size, count, count) } 5 => { let x_size = args.get(1).unwrap_or(&"32").parse::().unwrap(); let y_size = args.get(2).unwrap_or(&"32").parse::().unwrap(); let x_count = args.get(3).unwrap_or(&"8").parse::().unwrap(); let y_count = args.get(4).unwrap_or(&"8").parse::().unwrap(); (x_size, y_size, x_count, y_count) } _ => { panic!("invalid checkerboard function"); } }; let kind = if name == "transparent-checkerboard" { CheckerboardKind::BlackTransparent } else { CheckerboardKind::BlackGrey }; generate_checkerboard_image( border, x_size, y_size, x_count, y_count, kind, ) } _ => { panic!("Failed to load image {:?}", file.to_str()); } } } }; let tiling = tiling.map(|tile_size| tile_size as u16); let image_key = wrench.api.generate_image_key(); let mut txn = Transaction::new(); let external = item["external"].as_bool().unwrap_or(false); if external { // This indicates we want to simulate an external texture, // ensure it gets created as such let external_target = match item["external-target"].as_str() { Some("2d") => ImageBufferKind::Texture2D, Some("rect") => ImageBufferKind::TextureRect, Some(t) => panic!("Unsupported external texture target: {}", t), None => ImageBufferKind::Texture2D, }; let external_image_data = self.external_image_handler.as_mut().unwrap().add_image( &wrench.renderer.device, descriptor, external_target, image_data ); txn.add_image(image_key, descriptor, external_image_data, tiling); } else { txn.add_image(image_key, descriptor, image_data, tiling); } wrench.api.send_transaction(wrench.document_id, txn); let val = ( image_key, LayoutSize::new(descriptor.size.width as f32, descriptor.size.height as f32), ); self.image_map.insert(key, val); val } fn get_or_create_font(&mut self, desc: FontDescriptor, wrench: &mut Wrench) -> FontKey { let list_resources = self.list_resources; *self.fonts .entry(desc.clone()) .or_insert_with(|| match desc { FontDescriptor::Path { ref path, font_index, } => { if list_resources { println!("{}", path.to_string_lossy()); } let mut file = File::open(path).expect("Couldn't open font file"); let mut bytes = vec![]; file.read_to_end(&mut bytes) .expect("failed to read font file"); wrench.font_key_from_bytes(bytes, font_index) } FontDescriptor::Family { ref name } => wrench.font_key_from_name(name), FontDescriptor::Properties { ref family, weight, style, stretch, } => wrench.font_key_from_properties(family, weight, style, stretch), }) } pub fn allow_mipmaps(&mut self, allow_mipmaps: bool) { self.allow_mipmaps = allow_mipmaps; } pub fn set_font_render_mode(&mut self, render_mode: Option) { self.font_render_mode = render_mode; } fn get_or_create_font_instance( &mut self, font_key: FontKey, size: f32, bg_color: Option, flags: FontInstanceFlags, synthetic_italics: SyntheticItalics, wrench: &mut Wrench, ) -> FontInstanceKey { let font_render_mode = self.font_render_mode; *self.font_instances .entry((font_key, size.into(), flags, bg_color, synthetic_italics)) .or_insert_with(|| { wrench.add_font_instance( font_key, size, flags, font_render_mode, bg_color, synthetic_italics, ) }) } fn as_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option { item.as_hash()?; let tiling = item["tile-size"].as_i64(); let (image_key, image_dims) = match item["image"].as_str() { Some("invalid") => (ImageKey::DUMMY, LayoutSize::new(100.0, 100.0)), Some(filename) => { let mut file = self.aux_dir.clone(); file.push(filename); self.add_or_get_image(&file, tiling, item, wrench) } None => { warn!("No image provided for the image-mask!"); return None; } }; let image_rect = item["rect"] .as_rect() .unwrap_or_else(|| LayoutRect::from_size(image_dims)); Some(ImageMask { image: image_key, rect: image_rect, }) } fn handle_rect( &self, dl: &mut DisplayListBuilder, item: &Yaml, info: &CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "rect" } else { "bounds" }; let bounds = self.resolve_rect(&item[bounds_key]); let color = self.resolve_colorf(&item["color"]).unwrap_or(ColorF::BLACK); dl.push_rect(info, bounds, color); } fn handle_clear_rect( &self, dl: &mut DisplayListBuilder, item: &Yaml, info: &CommonItemProperties, ) { let bounds = item["bounds"].as_rect().expect("clear-rect type must have bounds"); dl.push_clear_rect(info, bounds); } fn handle_hit_test( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { info.clip_rect = try_intersect!( item["bounds"].as_rect().expect("hit-test type must have bounds"), &info.clip_rect ); if let Some(tag) = self.to_hit_testing_tag(&item["hit-testing-tag"]) { dl.push_hit_test( info.clip_rect, info.clip_chain_id, info.spatial_id, info.flags, tag, ); } } fn handle_line( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK); let orientation = item["orientation"] .as_str() .and_then(LineOrientation::from_str) .expect("line must have orientation"); let style = item["style"] .as_str() .and_then(LineStyle::from_str) .expect("line must have style"); let wavy_line_thickness = if let LineStyle::Wavy = style { item["thickness"].as_f32().expect("wavy lines must have a thickness") } else { 0.0 }; let area = if item["baseline"].is_badvalue() { let bounds_key = if item["type"].is_badvalue() { "rect" } else { "bounds" }; item[bounds_key] .as_rect() .expect("line type must have bounds") } else { // Legacy line representation let baseline = item["baseline"].as_f32().expect("line must have baseline"); let start = item["start"].as_f32().expect("line must have start"); let end = item["end"].as_f32().expect("line must have end"); let width = item["width"].as_f32().expect("line must have width"); match orientation { LineOrientation::Horizontal => { LayoutRect::from_origin_and_size( LayoutPoint::new(start, baseline), LayoutSize::new(end - start, width), ) } LineOrientation::Vertical => { LayoutRect::from_origin_and_size( LayoutPoint::new(baseline, start), LayoutSize::new(width, end - start), ) } } }; dl.push_line( info, &area, wavy_line_thickness, orientation, &color, style, ); } fn handle_gradient( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "gradient" } else { "bounds" }; let bounds = item[bounds_key] .as_rect() .expect("gradient must have bounds"); let gradient = item.as_gradient(dl); let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); dl.push_gradient( info, bounds, gradient, tile_size, tile_spacing ); } fn handle_radial_gradient( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "radial-gradient" } else { "bounds" }; let bounds = item[bounds_key] .as_rect() .expect("radial gradient must have bounds"); let gradient = item.as_radial_gradient(dl); let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); dl.push_radial_gradient( info, bounds, gradient, tile_size, tile_spacing, ); } fn handle_conic_gradient( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "conic-gradient" } else { "bounds" }; let bounds = item[bounds_key] .as_rect() .expect("conic gradient must have bounds"); let gradient = item.as_conic_gradient(dl); let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size()); let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero); dl.push_conic_gradient( info, bounds, gradient, tile_size, tile_spacing, ); } fn handle_border( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "border" } else { "bounds" }; let bounds = item[bounds_key].as_rect().expect("borders must have bounds"); let widths = item["width"] .as_vec_f32() .expect("borders must have width(s)"); let widths = broadcast(&widths, 4); let widths = LayoutSideOffsets::new(widths[0], widths[3], widths[2], widths[1]); let border_details = if let Some(border_type) = item["border-type"].as_str() { match border_type { "normal" => { let colors = item["color"] .as_vec_colorf() .expect("borders must have color(s)"); let styles = item["style"] .as_vec_string() .expect("borders must have style(s)"); let styles = styles .iter() .map(|s| match s.as_str() { "none" => BorderStyle::None, "solid" => BorderStyle::Solid, "double" => BorderStyle::Double, "dotted" => BorderStyle::Dotted, "dashed" => BorderStyle::Dashed, "hidden" => BorderStyle::Hidden, "ridge" => BorderStyle::Ridge, "inset" => BorderStyle::Inset, "outset" => BorderStyle::Outset, "groove" => BorderStyle::Groove, s => { panic!("Unknown border style '{}'", s); } }) .collect::>(); let radius = item["radius"] .as_border_radius() .unwrap_or_else(BorderRadius::zero); let colors = broadcast(&colors, 4); let styles = broadcast(&styles, 4); let top = BorderSide { color: colors[0], style: styles[0], }; let right = BorderSide { color: colors[1], style: styles[1], }; let bottom = BorderSide { color: colors[2], style: styles[2], }; let left = BorderSide { color: colors[3], style: styles[3], }; let do_aa = item["do_aa"].as_bool().unwrap_or(true); Some(BorderDetails::Normal(NormalBorder { top, left, bottom, right, radius, do_aa, })) } "image" | "gradient" | "radial-gradient" | "conic-gradient" => { let image_width = item["image-width"] .as_i64() .unwrap_or(bounds.width() as i64); let image_height = item["image-height"] .as_i64() .unwrap_or(bounds.height() as i64); let fill = item["fill"].as_bool().unwrap_or(false); let slice = if let Some(slice) = item["slice"].as_vec_u32() { broadcast(&slice, 4) } else { vec![widths.top as u32, widths.left as u32, widths.bottom as u32, widths.right as u32] }; let repeat_horizontal = match item["repeat-horizontal"] .as_str() .unwrap_or("stretch") { "stretch" => RepeatMode::Stretch, "repeat" => RepeatMode::Repeat, "round" => RepeatMode::Round, "space" => RepeatMode::Space, s => panic!("Unknown box border image repeat mode {}", s), }; let repeat_vertical = match item["repeat-vertical"] .as_str() .unwrap_or("stretch") { "stretch" => RepeatMode::Stretch, "repeat" => RepeatMode::Repeat, "round" => RepeatMode::Round, "space" => RepeatMode::Space, s => panic!("Unknown box border image repeat mode {}", s), }; let source = match border_type { "image" => { let file = rsrc_path(&item["image-source"], &self.aux_dir); let (image_key, _) = self .add_or_get_image(&file, None, item, wrench); NinePatchBorderSource::Image(image_key, ImageRendering::Auto) } "gradient" => { let gradient = item.as_gradient(dl); NinePatchBorderSource::Gradient(gradient) } "radial-gradient" => { let gradient = item.as_radial_gradient(dl); NinePatchBorderSource::RadialGradient(gradient) } "conic-gradient" => { let gradient = item.as_conic_gradient(dl); NinePatchBorderSource::ConicGradient(gradient) } _ => unreachable!("Unexpected border type"), }; Some(BorderDetails::NinePatch(NinePatchBorder { source, width: image_width as i32, height: image_height as i32, slice: SideOffsets2D::new(slice[0] as i32, slice[1] as i32, slice[2] as i32, slice[3] as i32), fill, repeat_horizontal, repeat_vertical, })) } _ => { println!("Unable to parse border {:?}", item); None } } } else { println!("Unable to parse border {:?}", item); None }; if let Some(details) = border_details { dl.push_border(info, bounds, widths, details); } } fn handle_box_shadow( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds_key = if item["type"].is_badvalue() { "box-shadow" } else { "bounds" }; let bounds = item[bounds_key] .as_rect() .expect("box shadow must have bounds"); let box_bounds = item["box-bounds"].as_rect().unwrap_or(bounds); let offset = self.resolve_vector(&item["offset"], LayoutVector2D::zero()); let color = item["color"] .as_colorf() .unwrap_or_else(|| ColorF::new(0.0, 0.0, 0.0, 1.0)); let blur_radius = item["blur-radius"].as_force_f32().unwrap_or(0.0); let spread_radius = item["spread-radius"].as_force_f32().unwrap_or(0.0); let border_radius = item["border-radius"] .as_border_radius() .unwrap_or_else(BorderRadius::zero); let clip_mode = if let Some(mode) = item["clip-mode"].as_str() { match mode { "outset" => BoxShadowClipMode::Outset, "inset" => BoxShadowClipMode::Inset, s => panic!("Unknown box shadow clip mode {}", s), } } else { BoxShadowClipMode::Outset }; dl.push_box_shadow( info, box_bounds, offset, color, blur_radius, spread_radius, border_radius, clip_mode, ); } fn handle_yuv_image( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, item: &Yaml, info: &mut CommonItemProperties, ) { // TODO(gw): Support other YUV color depth and spaces. let color_depth = ColorDepth::Color8; let color_space = YuvColorSpace::Rec709; let color_range = ColorRange::Limited; let yuv_data = match item["format"].as_str().expect("no format supplied") { "planar" => { let y_path = rsrc_path(&item["src-y"], &self.aux_dir); let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); let u_path = rsrc_path(&item["src-u"], &self.aux_dir); let (u_key, _) = self.add_or_get_image(&u_path, None, item, wrench); let v_path = rsrc_path(&item["src-v"], &self.aux_dir); let (v_key, _) = self.add_or_get_image(&v_path, None, item, wrench); YuvData::PlanarYCbCr(y_key, u_key, v_key) } "nv12" => { let y_path = rsrc_path(&item["src-y"], &self.aux_dir); let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir); let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench); YuvData::NV12(y_key, uv_key) } "p010" => { let y_path = rsrc_path(&item["src-y"], &self.aux_dir); let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench); let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir); let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench); YuvData::P010(y_key, uv_key) } "interleaved" => { let yuv_path = rsrc_path(&item["src"], &self.aux_dir); let (yuv_key, _) = self.add_or_get_image(&yuv_path, None, item, wrench); YuvData::InterleavedYCbCr(yuv_key) } _ => { panic!("unexpected yuv format"); } }; let bounds = item["bounds"].as_vec_f32().unwrap(); let bounds = LayoutRect::from_origin_and_size( LayoutPoint::new(bounds[0], bounds[1]), LayoutSize::new(bounds[2], bounds[3]), ); dl.push_yuv_image( info, bounds, yuv_data, color_depth, color_space, color_range, ImageRendering::Auto, ); } fn handle_image( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, item: &Yaml, info: &mut CommonItemProperties, ) { let filename = &item[if item["type"].is_badvalue() { "image" } else { "src" }]; let tiling = item["tile-size"].as_i64(); let file = rsrc_path(filename, &self.aux_dir); let (image_key, image_dims) = self.add_or_get_image(&file, tiling, item, wrench); let bounds_raws = item["bounds"].as_vec_f32().unwrap(); let bounds = if bounds_raws.len() == 2 { LayoutRect::from_origin_and_size(LayoutPoint::new(bounds_raws[0], bounds_raws[1]), image_dims) } else if bounds_raws.len() == 4 { LayoutRect::from_origin_and_size( LayoutPoint::new(bounds_raws[0], bounds_raws[1]), LayoutSize::new(bounds_raws[2], bounds_raws[3]), ) } else { panic!( "image expected 2 or 4 values in bounds, got '{:?}'", item["bounds"] ); }; let rendering = match item["rendering"].as_str() { Some("auto") | None => ImageRendering::Auto, Some("crisp-edges") => ImageRendering::CrispEdges, Some("pixelated") => ImageRendering::Pixelated, Some(_) => panic!( "ImageRendering can be auto, crisp-edges, or pixelated -- got {:?}", item ), }; let alpha_type = match item["alpha-type"].as_str() { Some("premultiplied-alpha") | None => AlphaType::PremultipliedAlpha, Some("alpha") => AlphaType::Alpha, Some(_) => panic!( "AlphaType can be premultiplied-alpha or alpha -- got {:?}", item ), }; let color = item["color"] .as_colorf() .unwrap_or_else(|| ColorF::WHITE); let stretch_size = item["stretch-size"].as_size(); let tile_spacing = item["tile-spacing"].as_size(); if stretch_size.is_none() && tile_spacing.is_none() { dl.push_image( info, bounds, rendering, alpha_type, image_key, color, ); } else { dl.push_repeating_image( info, bounds, stretch_size.unwrap_or(image_dims), tile_spacing.unwrap_or_else(LayoutSize::zero), rendering, alpha_type, image_key, color, ); } } fn handle_text( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, item: &Yaml, info: &mut CommonItemProperties, ) { let size = item["size"].as_pt_to_f32().unwrap_or(16.0); let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK); let bg_color = item["bg-color"].as_colorf().map(|c| c.into()); let synthetic_italics = if let Some(angle) = item["synthetic-italics"].as_f32() { SyntheticItalics::from_degrees(angle) } else if item["synthetic-italics"].as_bool().unwrap_or(false) { SyntheticItalics::enabled() } else { SyntheticItalics::disabled() }; let mut flags = FontInstanceFlags::empty(); if item["synthetic-bold"].as_bool().unwrap_or(false) { flags |= FontInstanceFlags::SYNTHETIC_BOLD; } if item["embedded-bitmaps"].as_bool().unwrap_or(false) { flags |= FontInstanceFlags::EMBEDDED_BITMAPS; } if item["transpose"].as_bool().unwrap_or(false) { flags |= FontInstanceFlags::TRANSPOSE; } if item["flip-x"].as_bool().unwrap_or(false) { flags |= FontInstanceFlags::FLIP_X; } if item["flip-y"].as_bool().unwrap_or(false) { flags |= FontInstanceFlags::FLIP_Y; } assert!( item["blur-radius"].is_badvalue(), "text no longer has a blur radius, use PushShadow and PopAllShadows" ); let desc = FontDescriptor::from_yaml(item, &self.aux_dir); let font_key = self.get_or_create_font(desc, wrench); let font_instance_key = self.get_or_create_font_instance(font_key, size, bg_color, flags, synthetic_italics, wrench); assert!( !(item["glyphs"].is_badvalue() && item["text"].is_badvalue()), "text item had neither text nor glyphs!" ); let (glyphs, rect) = if item["text"].is_badvalue() { // if glyphs are specified, then the glyph positions can have the // origin baked in. let origin = item["origin"] .as_point() .unwrap_or(LayoutPoint::new(0.0, 0.0)); let glyph_indices = item["glyphs"].as_vec_u32().unwrap(); let glyph_offsets = item["offsets"].as_vec_f32().unwrap(); assert_eq!(glyph_offsets.len(), glyph_indices.len() * 2); let glyphs = glyph_indices .iter() .enumerate() .map(|k| { GlyphInstance { index: *k.1, // In the future we want to change the API to be relative, eliminating this point: LayoutPoint::new( origin.x + glyph_offsets[k.0 * 2], origin.y + glyph_offsets[k.0 * 2 + 1], ), } }) .collect::>(); // TODO(gw): We could optionally use the WR API to query glyph dimensions // here and calculate the bounding region here if we want to. let rect = item["bounds"] .as_rect() .expect("Text items with glyphs require bounds [for now]"); (glyphs, rect) } else { let text = item["text"].as_str().unwrap(); let origin = item["origin"] .as_point() .expect("origin required for text without glyphs"); let (glyph_indices, glyph_positions, bounds) = wrench.layout_simple_ascii( font_key, font_instance_key, text, size, origin, flags, ); let glyphs = glyph_indices .iter() .zip(glyph_positions) .map(|arg| { GlyphInstance { index: *arg.0 as u32, point: arg.1, } }) .collect::>(); (glyphs, bounds) }; dl.push_text( info, rect, &glyphs, font_instance_key, color, None, ); } fn handle_iframe( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { let bounds = item["bounds"].as_rect().expect("iframe must have bounds"); let pipeline_id = item["id"].as_pipeline_id().unwrap(); let ignore = item["ignore_missing_pipeline"].as_bool().unwrap_or(true); dl.push_iframe( bounds, info.clip_rect, &SpaceAndClipInfo { spatial_id: info.spatial_id, clip_chain_id: info.clip_chain_id }, pipeline_id, ignore ); } fn get_item_type_from_yaml(item: &Yaml) -> &str { let shorthands = [ "rect", "image", "text", "glyphs", "box-shadow", // Note: box_shadow shorthand check has to come before border. "border", "gradient", "radial-gradient", "conic-gradient" ]; for shorthand in shorthands.iter() { if !item[*shorthand].is_badvalue() { return shorthand; } } item["type"].as_str().unwrap_or("unknown") } fn add_display_list_items_from_yaml( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml_items: &[Yaml], ) { // A very large number (but safely far away from finite limits of f32) let big_number = 1.0e30; // A rect that should in practical terms serve as a no-op for clipping let full_clip = LayoutRect::from_origin_and_size( LayoutPoint::new(-big_number / 2.0, -big_number / 2.0), LayoutSize::new(big_number, big_number)); for item in yaml_items { let item_type = Self::get_item_type_from_yaml(item); let spatial_id = self.to_spatial_id(&item["spatial-id"], dl.pipeline_id); if let Some(spatial_id) = spatial_id { self.spatial_id_stack.push(spatial_id); } let clip_rect = item["clip-rect"].as_rect().unwrap_or(full_clip); let clip_chain_id = self.to_clip_chain_id(&item["clip-chain"], dl).unwrap_or(ClipChainId::INVALID); let mut flags = PrimitiveFlags::default(); for (key, flag) in [ ("backface-visible", PrimitiveFlags::IS_BACKFACE_VISIBLE), ("scrollbar-container", PrimitiveFlags::IS_SCROLLBAR_CONTAINER), ("prefer-compositor-surface", PrimitiveFlags::PREFER_COMPOSITOR_SURFACE), ] { if let Some(value) = item[key].as_bool() { flags.set(flag, value); } } let mut info = CommonItemProperties { clip_rect, clip_chain_id, spatial_id: self.top_space(), flags, }; match item_type { "rect" => self.handle_rect(dl, item, &info), "hit-test" => self.handle_hit_test(dl, item, &mut info), "clear-rect" => self.handle_clear_rect(dl, item, &info), "line" => self.handle_line(dl, item, &mut info), "image" => self.handle_image(dl, wrench, item, &mut info), "yuv-image" => self.handle_yuv_image(dl, wrench, item, &mut info), "text" | "glyphs" => self.handle_text(dl, wrench, item, &mut info), "scroll-frame" => self.handle_scroll_frame(dl, wrench, item), "sticky-frame" => self.handle_sticky_frame(dl, wrench, item), "clip" => self.handle_clip(dl, wrench, item), "clip-chain" => self.handle_clip_chain(dl, item), "border" => self.handle_border(dl, wrench, item, &mut info), "gradient" => self.handle_gradient(dl, item, &mut info), "radial-gradient" => self.handle_radial_gradient(dl, item, &mut info), "conic-gradient" => self.handle_conic_gradient(dl, item, &mut info), "box-shadow" => self.handle_box_shadow(dl, item, &mut info), "iframe" => self.handle_iframe(dl, item, &mut info), "stacking-context" => { self.add_stacking_context_from_yaml(dl, wrench, item, IsRoot(false), &mut info) } "reference-frame" => self.handle_reference_frame(dl, wrench, item), "computed-frame" => self.handle_computed_frame(dl, wrench, item), "shadow" => self.handle_push_shadow(dl, item, &mut info), "pop-all-shadows" => self.handle_pop_all_shadows(dl), "backdrop-filter" => self.handle_backdrop_filter(dl, item, &mut info), _ => println!("Skipping unknown item type: {:?}", item), } if spatial_id.is_some() { self.spatial_id_stack.pop().unwrap(); } } } fn next_spatial_key(&mut self) -> SpatialTreeItemKey { let key = SpatialTreeItemKey::new(self.next_spatial_key, 0); self.next_spatial_key += 1; key } fn handle_scroll_frame( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml, ) { let clip_rect = yaml["bounds"] .as_rect() .expect("scroll frame must have a bounds"); let content_size = yaml["content-size"].as_size().unwrap_or_else(|| clip_rect.size()); let content_rect = LayoutRect::from_origin_and_size(clip_rect.min, content_size); let external_scroll_offset = yaml["external-scroll-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero); let scroll_generation = yaml["scroll-generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64); let has_scroll_linked_effect = yaml["has-scroll-linked-effect"].as_bool().map_or(HasScrollLinkedEffect::default(), |v| if v { HasScrollLinkedEffect::Yes } else { HasScrollLinkedEffect::No } ); let numeric_id = yaml["id"].as_i64().map(|id| id as u64); let external_id = ExternalScrollId(self.next_external_scroll_id, dl.pipeline_id); self.next_external_scroll_id += 1; if let Some(vector) = yaml["scroll-offset"].as_vector() { self.scroll_offsets.insert( external_id, vec![SampledScrollOffset { offset: vector, generation: APZScrollGeneration::default(), }], ); } if !yaml["scroll-offsets"].is_badvalue() { let mut offsets = Vec::new(); for entry in yaml["scroll-offsets"].as_vec().unwrap() { let offset = entry["offset"].as_vector().unwrap_or(LayoutVector2D::zero()); let generation = entry["generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64); offsets.push(SampledScrollOffset { offset, generation }); } self.scroll_offsets.insert(external_id, offsets); } let clip_to_frame = yaml["clip-to-frame"].as_bool().unwrap_or(false); let clip_id = if clip_to_frame { Some(dl.define_clip_rect( self.top_space(), clip_rect, )) } else { None }; let spatial_id = dl.define_scroll_frame( self.top_space(), external_id, content_rect, clip_rect, external_scroll_offset, scroll_generation, has_scroll_linked_effect, self.next_spatial_key(), ); if let Some(numeric_id) = numeric_id { self.add_spatial_id_mapping(numeric_id, spatial_id); if let Some(clip_id) = clip_id { self.add_clip_id_mapping(numeric_id, clip_id); } } if let Some(yaml_items) = yaml["items"].as_vec() { self.spatial_id_stack.push(spatial_id); self.add_display_list_items_from_yaml(dl, wrench, yaml_items); self.spatial_id_stack.pop().unwrap(); } } fn handle_sticky_frame( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml, ) { let bounds = yaml["bounds"].as_rect().expect("sticky frame must have a bounds"); let numeric_id = yaml["id"].as_i64().map(|id| id as u64); let real_id = dl.define_sticky_frame( *self.spatial_id_stack.last().unwrap(), bounds, SideOffsets2D::new( yaml["margin-top"].as_f32(), yaml["margin-right"].as_f32(), yaml["margin-bottom"].as_f32(), yaml["margin-left"].as_f32(), ), yaml["vertical-offset-bounds"].as_sticky_offset_bounds(), yaml["horizontal-offset-bounds"].as_sticky_offset_bounds(), yaml["previously-applied-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero), self.next_spatial_key(), ); if let Some(numeric_id) = numeric_id { self.add_spatial_id_mapping(numeric_id, real_id); } if let Some(yaml_items) = yaml["items"].as_vec() { self.spatial_id_stack.push(real_id); self.add_display_list_items_from_yaml(dl, wrench, yaml_items); self.spatial_id_stack.pop().unwrap(); } } fn resolve_binding<'a>( &'a self, yaml: &'a Yaml, ) -> &'a Yaml { if let Some(keyframes) = &self.keyframes { if let Some(s) = yaml.as_str() { const PREFIX: &str = "key("; const SUFFIX: &str = ")"; if let Some(key) = s.strip_prefix(PREFIX).and_then(|s| s.strip_suffix(SUFFIX)) { return &keyframes[key][self.requested_frame]; } } } yaml } fn resolve_colorf( &self, yaml: &Yaml, ) -> Option { self.resolve_binding(yaml) .as_colorf() } fn resolve_rect( &self, yaml: &Yaml, ) -> LayoutRect { self.resolve_binding(yaml) .as_rect() .unwrap_or_else(|| panic!("invalid rect {:?}", yaml)) } fn resolve_vector( &self, yaml: &Yaml, default: LayoutVector2D, ) -> LayoutVector2D { self.resolve_binding(yaml) .as_vector() .unwrap_or(default) } fn handle_push_shadow( &mut self, dl: &mut DisplayListBuilder, yaml: &Yaml, info: &mut CommonItemProperties, ) { let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0); let offset = yaml["offset"].as_vector().unwrap_or_else(LayoutVector2D::zero); let color = yaml["color"].as_colorf().unwrap_or(ColorF::BLACK); dl.push_shadow( &SpaceAndClipInfo { spatial_id: info.spatial_id, clip_chain_id: info.clip_chain_id }, Shadow { blur_radius, offset, color, }, true, ); } fn handle_pop_all_shadows(&mut self, dl: &mut DisplayListBuilder) { dl.pop_all_shadows(); } fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) { let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id"); let clip_ids: Vec = yaml["clips"] .as_vec_u64() .unwrap_or_default() .iter() .map(|id| self.user_clip_id_map[id]) .collect(); let parent = self.to_clip_chain_id(&yaml["parent"], builder); let real_id = builder.define_clip_chain(parent, clip_ids); self.add_clip_chain_id_mapping(numeric_id as u64, real_id); } fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) { let numeric_id = yaml["id"].as_i64(); let spatial_id = self.top_space(); let complex_clips = yaml["complex"].as_complex_clip_regions(); let mut clip_id = None; if let Some(clip_rect) = yaml["bounds"].as_rect() { clip_id = Some(dl.define_clip_rect( spatial_id, clip_rect, )); } if let Some(image_mask) = self.as_image_mask(&yaml["image-mask"], wrench) { assert!(clip_id.is_none(), "invalid clip definition"); clip_id = Some(dl.define_clip_image_mask( spatial_id, image_mask, &[], FillRule::Nonzero, )); } if !complex_clips.is_empty() { // Only 1 complex clip is supported per clip (todo: change yaml format) assert_eq!(complex_clips.len(), 1); assert!(clip_id.is_none(), "invalid clip definition"); clip_id = Some(dl.define_clip_rounded_rect( spatial_id, complex_clips[0], )); } if let Some(clip_id) = clip_id { if let Some(numeric_id) = numeric_id { self.add_clip_id_mapping(numeric_id as u64, clip_id); } } } fn push_reference_frame( &mut self, dl: &mut DisplayListBuilder, default_bounds: impl Fn() -> LayoutRect, yaml: &Yaml, ) -> SpatialId { let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); let default_transform_origin = LayoutPoint::new( bounds.min.x + bounds.width() * 0.5, bounds.min.y + bounds.height() * 0.5, ); let transform_style = yaml["transform-style"] .as_transform_style() .unwrap_or(TransformStyle::Flat); let transform_origin = yaml["transform-origin"] .as_point() .unwrap_or(default_transform_origin); assert!( yaml["transform"].is_badvalue() || yaml["perspective"].is_badvalue(), "Should have one of either transform or perspective" ); let perspective_origin = yaml["perspective-origin"] .as_point() .unwrap_or(default_transform_origin); let reference_frame_kind = if !yaml["perspective"].is_badvalue() { ReferenceFrameKind::Perspective { scrolling_relative_to: None } } else { ReferenceFrameKind::Transform { is_2d_scale_translation: false, should_snap: false, paired_with_perspective: yaml["paired-with-perspective"].as_bool().unwrap_or(false), } }; let transform = yaml["transform"] .as_transform(&transform_origin); let perspective = match yaml["perspective"].as_f32() { Some(value) if value != 0.0 => { Some(make_perspective(perspective_origin, value as f32)) } Some(..) => None, _ => yaml["perspective"].as_matrix4d(), }; let reference_frame_id = dl.push_reference_frame( bounds.min, *self.spatial_id_stack.last().unwrap(), transform_style, transform.or(perspective).unwrap_or_default().into(), reference_frame_kind, self.next_spatial_key(), ); let numeric_id = yaml["id"].as_i64(); if let Some(numeric_id) = numeric_id { self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id); } reference_frame_id } fn handle_reference_frame( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml, ) { let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); let real_id = self.push_reference_frame(dl, default_bounds, yaml); self.spatial_id_stack.push(real_id); if let Some(yaml_items) = yaml["items"].as_vec() { self.add_display_list_items_from_yaml(dl, wrench, yaml_items); } self.spatial_id_stack.pop().unwrap(); dl.pop_reference_frame(); } fn push_computed_frame( &mut self, dl: &mut DisplayListBuilder, default_bounds: impl Fn() -> LayoutRect, yaml: &Yaml, ) -> SpatialId { let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); let scale_from = yaml["scale-from"].as_size(); let vertical_flip = yaml["vertical-flip"].as_bool().unwrap_or(false); let rotation = yaml["rotation"].as_rotation().unwrap_or(Rotation::Degree0); let reference_frame_id = dl.push_computed_frame( bounds.min, *self.spatial_id_stack.last().unwrap(), scale_from, vertical_flip, rotation, self.next_spatial_key(), ); let numeric_id = yaml["id"].as_i64(); if let Some(numeric_id) = numeric_id { self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id); } reference_frame_id } fn handle_computed_frame( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml, ) { let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); let real_id = self.push_computed_frame(dl, default_bounds, yaml); self.spatial_id_stack.push(real_id); if let Some(yaml_items) = yaml["items"].as_vec() { self.add_display_list_items_from_yaml(dl, wrench, yaml_items); } self.spatial_id_stack.pop().unwrap(); dl.pop_reference_frame(); } fn add_stacking_context_from_yaml( &mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml, IsRoot(is_root): IsRoot, info: &mut CommonItemProperties, ) { let default_bounds = || LayoutRect::from_size(wrench.window_size_f32()); let mut bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds); let pushed_reference_frame = if !yaml["transform"].is_badvalue() || !yaml["perspective"].is_badvalue() { let reference_frame_id = self.push_reference_frame(dl, default_bounds, yaml); self.spatial_id_stack.push(reference_frame_id); bounds.max -= bounds.min.to_vector(); bounds.min = LayoutPoint::zero(); true } else { false }; let clip_chain_id = self.to_clip_chain_id(&yaml["clip-chain"], dl); let transform_style = yaml["transform-style"] .as_transform_style() .unwrap_or(TransformStyle::Flat); let mix_blend_mode = yaml["mix-blend-mode"] .as_mix_blend_mode() .unwrap_or(MixBlendMode::Normal); let raster_space = yaml["raster-space"] .as_raster_space() .unwrap_or(RasterSpace::Screen); let is_blend_container = yaml["blend-container"].as_bool().unwrap_or(false); let wraps_backdrop_filter = yaml["wraps-backdrop-filter"].as_bool().unwrap_or(false); if is_root { if let Some(vector) = yaml["scroll-offset"].as_vector() { let external_id = ExternalScrollId(0, dl.pipeline_id); self.scroll_offsets.insert( external_id, vec![SampledScrollOffset { offset: vector, generation: APZScrollGeneration::default(), }], ); } } let filters = yaml["filters"].as_vec_filter_op().unwrap_or_default(); let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or_default(); let filter_primitives = yaml["filter-primitives"].as_vec_filter_primitive().unwrap_or_default(); let mut flags = StackingContextFlags::empty(); flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container); flags.set(StackingContextFlags::WRAPS_BACKDROP_FILTER, wraps_backdrop_filter); dl.push_stacking_context( bounds.min, *self.spatial_id_stack.last().unwrap(), info.flags, clip_chain_id, transform_style, mix_blend_mode, &filters, &filter_datas, &filter_primitives, raster_space, flags, ); if let Some(yaml_items) = yaml["items"].as_vec() { self.add_display_list_items_from_yaml(dl, wrench, yaml_items); } dl.pop_stacking_context(); if pushed_reference_frame { self.spatial_id_stack.pop().unwrap(); dl.pop_reference_frame(); } } fn handle_backdrop_filter( &mut self, dl: &mut DisplayListBuilder, item: &Yaml, info: &mut CommonItemProperties, ) { info.clip_rect = try_intersect!( self.resolve_rect(&item["bounds"]), &info.clip_rect ); let filters = item["filters"].as_vec_filter_op().unwrap_or_default(); let filter_datas = item["filter-datas"].as_vec_filter_data().unwrap_or_default(); let filter_primitives = item["filter-primitives"].as_vec_filter_primitive().unwrap_or_default(); dl.push_backdrop_filter( info, &filters, &filter_datas, &filter_primitives, ); } } impl WrenchThing for YamlFrameReader { fn do_frame(&mut self, wrench: &mut Wrench) -> u32 { let mut should_build_yaml = false; // If YAML isn't read yet, or watching source file, reload from disk. if self.yaml_string.is_empty() || self.watch_source { self.yaml_string = std::fs::read_to_string(&self.yaml_path) .unwrap_or_else(|_| panic!("YAML '{:?}' doesn't exist", self.yaml_path)); should_build_yaml = true; } // Evaluate conditions that require parsing the YAML. if self.built_frame != self.requested_frame { // Requested frame has changed should_build_yaml = true; } // Build the DL from YAML if required if should_build_yaml { self.build(wrench); } // Determine whether to send a new DL, or just refresh. if should_build_yaml || wrench.should_rebuild_display_lists() { wrench.begin_frame(); wrench.send_lists( self.frame_count, self.display_lists.clone(), &self.scroll_offsets, ); } else { wrench.refresh(); } self.frame_count += 1; self.frame_count } fn next_frame(&mut self) { let mut max_frame_count = 0; if let Some(ref keyframes) = self.keyframes { for (_, values) in keyframes.as_hash().unwrap() { max_frame_count = max_frame_count.max(values.as_vec().unwrap().len()); } } if self.requested_frame + 1 < max_frame_count { self.requested_frame += 1; } } fn prev_frame(&mut self) { if self.requested_frame > 0 { self.requested_frame -= 1; } } }