summaryrefslogtreecommitdiffstats
path: root/gfx/wr/wrench/src/yaml_helper.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/wrench/src/yaml_helper.rs')
-rw-r--r--gfx/wr/wrench/src/yaml_helper.rs923
1 files changed, 923 insertions, 0 deletions
diff --git a/gfx/wr/wrench/src/yaml_helper.rs b/gfx/wr/wrench/src/yaml_helper.rs
new file mode 100644
index 0000000000..c28fad04ce
--- /dev/null
+++ b/gfx/wr/wrench/src/yaml_helper.rs
@@ -0,0 +1,923 @@
+/* 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::{Angle, Size2D};
+use crate::parse_function::parse_function;
+use std::f32;
+use std::str::FromStr;
+use webrender::api::*;
+use webrender::api::units::*;
+use yaml_rust::{Yaml, YamlLoader};
+
+pub trait YamlHelper {
+ fn as_f32(&self) -> Option<f32>;
+ fn as_force_f32(&self) -> Option<f32>;
+ fn as_vec_f32(&self) -> Option<Vec<f32>>;
+ fn as_vec_u32(&self) -> Option<Vec<u32>>;
+ fn as_vec_u64(&self) -> Option<Vec<u64>>;
+ fn as_pipeline_id(&self) -> Option<PipelineId>;
+ fn as_rect(&self) -> Option<LayoutRect>;
+ fn as_size(&self) -> Option<LayoutSize>;
+ fn as_point(&self) -> Option<LayoutPoint>;
+ fn as_vector(&self) -> Option<LayoutVector2D>;
+ fn as_matrix4d(&self) -> Option<LayoutTransform>;
+ fn as_transform(&self, transform_origin: &LayoutPoint) -> Option<LayoutTransform>;
+ fn as_colorf(&self) -> Option<ColorF>;
+ fn as_vec_colorf(&self) -> Option<Vec<ColorF>>;
+ fn as_px_to_f32(&self) -> Option<f32>;
+ fn as_pt_to_f32(&self) -> Option<f32>;
+ fn as_vec_string(&self) -> Option<Vec<String>>;
+ fn as_border_radius_component(&self) -> LayoutSize;
+ fn as_border_radius(&self) -> Option<BorderRadius>;
+ fn as_transform_style(&self) -> Option<TransformStyle>;
+ fn as_raster_space(&self) -> Option<RasterSpace>;
+ fn as_clip_mode(&self) -> Option<ClipMode>;
+ fn as_mix_blend_mode(&self) -> Option<MixBlendMode>;
+ fn as_filter_op(&self) -> Option<FilterOp>;
+ fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>>;
+ fn as_filter_data(&self) -> Option<FilterData>;
+ fn as_vec_filter_data(&self) -> Option<Vec<FilterData>>;
+ fn as_filter_input(&self) -> Option<FilterPrimitiveInput>;
+ fn as_filter_primitive(&self) -> Option<FilterPrimitive>;
+ fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>>;
+ fn as_color_space(&self) -> Option<ColorSpace>;
+ fn as_complex_clip_region(&self) -> ComplexClipRegion;
+ fn as_sticky_offset_bounds(&self) -> StickyOffsetBounds;
+ fn as_gradient(&self, dl: &mut DisplayListBuilder) -> Gradient;
+ fn as_radial_gradient(&self, dl: &mut DisplayListBuilder) -> RadialGradient;
+ fn as_conic_gradient(&self, dl: &mut DisplayListBuilder) -> ConicGradient;
+ fn as_complex_clip_regions(&self) -> Vec<ComplexClipRegion>;
+ fn as_rotation(&self) -> Option<Rotation>;
+}
+
+fn string_to_color(color: &str) -> Option<ColorF> {
+ match color {
+ "red" => Some(ColorF::new(1.0, 0.0, 0.0, 1.0)),
+ "green" => Some(ColorF::new(0.0, 1.0, 0.0, 1.0)),
+ "blue" => Some(ColorF::new(0.0, 0.0, 1.0, 1.0)),
+ "white" => Some(ColorF::new(1.0, 1.0, 1.0, 1.0)),
+ "black" => Some(ColorF::new(0.0, 0.0, 0.0, 1.0)),
+ "yellow" => Some(ColorF::new(1.0, 1.0, 0.0, 1.0)),
+ "cyan" => Some(ColorF::new(0.0, 1.0, 1.0, 1.0)),
+ "magenta" => Some(ColorF::new(1.0, 0.0, 1.0, 1.0)),
+ "transparent" => Some(ColorF::new(1.0, 1.0, 1.0, 0.0)),
+ s => {
+ let items: Vec<f32> = s.split_whitespace()
+ .map(|s| f32::from_str(s).unwrap())
+ .collect();
+ if items.len() == 3 {
+ Some(ColorF::new(
+ items[0] / 255.0,
+ items[1] / 255.0,
+ items[2] / 255.0,
+ 1.0,
+ ))
+ } else if items.len() == 4 {
+ Some(ColorF::new(
+ items[0] / 255.0,
+ items[1] / 255.0,
+ items[2] / 255.0,
+ items[3],
+ ))
+ } else {
+ None
+ }
+ }
+ }
+}
+
+pub trait StringEnum: Sized {
+ fn from_str(_: &str) -> Option<Self>;
+ fn as_str(&self) -> &'static str;
+}
+
+macro_rules! define_string_enum {
+ ($T:ident, [ $( $y:ident = $x:expr ),* ]) => {
+ impl StringEnum for $T {
+ fn from_str(text: &str) -> Option<$T> {
+ match text {
+ $( $x => Some($T::$y), )*
+ _ => {
+ println!("Unrecognized {} value '{}'", stringify!($T), text);
+ None
+ }
+ }
+ }
+ fn as_str(&self) -> &'static str {
+ match *self {
+ $( $T::$y => $x, )*
+ }
+ }
+ }
+ }
+}
+
+define_string_enum!(TransformStyle, [Flat = "flat", Preserve3D = "preserve-3d"]);
+
+define_string_enum!(
+ MixBlendMode,
+ [
+ Normal = "normal",
+ Multiply = "multiply",
+ Screen = "screen",
+ Overlay = "overlay",
+ Darken = "darken",
+ Lighten = "lighten",
+ ColorDodge = "color-dodge",
+ ColorBurn = "color-burn",
+ HardLight = "hard-light",
+ SoftLight = "soft-light",
+ Difference = "difference",
+ Exclusion = "exclusion",
+ Hue = "hue",
+ Saturation = "saturation",
+ Color = "color",
+ Luminosity = "luminosity",
+ PlusLighter = "plus-lighter"
+ ]
+);
+
+define_string_enum!(
+ LineOrientation,
+ [Horizontal = "horizontal", Vertical = "vertical"]
+);
+
+define_string_enum!(
+ LineStyle,
+ [
+ Solid = "solid",
+ Dotted = "dotted",
+ Dashed = "dashed",
+ Wavy = "wavy"
+ ]
+);
+
+define_string_enum!(ClipMode, [Clip = "clip", ClipOut = "clip-out"]);
+
+define_string_enum!(
+ ComponentTransferFuncType,
+ [
+ Identity = "Identity",
+ Table = "Table",
+ Discrete = "Discrete",
+ Linear = "Linear",
+ Gamma = "Gamma"
+ ]
+);
+
+define_string_enum!(
+ ColorSpace,
+ [
+ Srgb = "srgb",
+ LinearRgb = "linear-rgb"
+ ]
+);
+
+// Rotate around `axis` by `degrees` angle
+fn make_rotation(
+ origin: &LayoutPoint,
+ degrees: f32,
+ axis_x: f32,
+ axis_y: f32,
+ axis_z: f32,
+) -> LayoutTransform {
+ let pre_transform = LayoutTransform::translation(-origin.x, -origin.y, -0.0);
+ let post_transform = LayoutTransform::translation(origin.x, origin.y, 0.0);
+
+ let theta = 2.0f32 * f32::consts::PI - degrees.to_radians();
+ let transform =
+ LayoutTransform::identity().pre_rotate(axis_x, axis_y, axis_z, Angle::radians(theta));
+
+ pre_transform.then(&transform).then(&post_transform)
+}
+
+pub fn make_perspective(
+ origin: LayoutPoint,
+ perspective: f32,
+) -> LayoutTransform {
+ let pre_transform = LayoutTransform::translation(-origin.x, -origin.y, -0.0);
+ let post_transform = LayoutTransform::translation(origin.x, origin.y, 0.0);
+ let transform = LayoutTransform::perspective(perspective);
+ pre_transform.then(&transform).then(&post_transform)
+}
+
+// Create a skew matrix, specified in degrees.
+fn make_skew(
+ skew_x: f32,
+ skew_y: f32,
+) -> LayoutTransform {
+ let alpha = Angle::radians(skew_x.to_radians());
+ let beta = Angle::radians(skew_y.to_radians());
+ LayoutTransform::skew(alpha, beta)
+}
+
+impl YamlHelper for Yaml {
+ fn as_f32(&self) -> Option<f32> {
+ match *self {
+ Yaml::Integer(iv) => Some(iv as f32),
+ Yaml::Real(ref sv) => f32::from_str(sv.as_str()).ok(),
+ _ => None,
+ }
+ }
+
+ fn as_force_f32(&self) -> Option<f32> {
+ match *self {
+ Yaml::Integer(iv) => Some(iv as f32),
+ Yaml::String(ref sv) | Yaml::Real(ref sv) => f32::from_str(sv.as_str()).ok(),
+ _ => None,
+ }
+ }
+
+ fn as_vec_f32(&self) -> Option<Vec<f32>> {
+ match *self {
+ Yaml::String(ref s) | Yaml::Real(ref s) => s.split_whitespace()
+ .map(f32::from_str)
+ .collect::<Result<Vec<_>, _>>()
+ .ok(),
+ Yaml::Array(ref v) => v.iter()
+ .map(|v| match *v {
+ Yaml::Integer(k) => Ok(k as f32),
+ Yaml::String(ref k) | Yaml::Real(ref k) => f32::from_str(k).map_err(|_| false),
+ _ => Err(false),
+ })
+ .collect::<Result<Vec<_>, _>>()
+ .ok(),
+ Yaml::Integer(k) => Some(vec![k as f32]),
+ _ => None,
+ }
+ }
+
+ fn as_vec_u32(&self) -> Option<Vec<u32>> {
+ self.as_vec().map(|v| v.iter().map(|v| v.as_i64().unwrap() as u32).collect())
+ }
+
+ fn as_vec_u64(&self) -> Option<Vec<u64>> {
+ self.as_vec().map(|v| v.iter().map(|v| v.as_i64().unwrap() as u64).collect())
+ }
+
+ fn as_pipeline_id(&self) -> Option<PipelineId> {
+ if let Some(v) = self.as_vec() {
+ let a = v.get(0).and_then(|v| v.as_i64()).map(|v| v as u32);
+ let b = v.get(1).and_then(|v| v.as_i64()).map(|v| v as u32);
+ match (a, b) {
+ (Some(a), Some(b)) if v.len() == 2 => Some(PipelineId(a, b)),
+ _ => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ fn as_px_to_f32(&self) -> Option<f32> {
+ self.as_force_f32()
+ }
+
+ fn as_pt_to_f32(&self) -> Option<f32> {
+ self.as_force_f32().map(|fv| fv * 16. / 12.)
+ }
+
+ fn as_rect(&self) -> Option<LayoutRect> {
+ self.as_vec_f32().and_then(|v| match v.as_slice() {
+ &[x, y, width, height] => Some(LayoutRect::from_origin_and_size(
+ LayoutPoint::new(x, y),
+ LayoutSize::new(width, height),
+ )),
+ _ => None,
+ })
+ }
+
+ fn as_size(&self) -> Option<LayoutSize> {
+ if self.is_badvalue() {
+ return None;
+ }
+
+ if let Some(nums) = self.as_vec_f32() {
+ if nums.len() == 2 {
+ return Some(LayoutSize::new(nums[0], nums[1]));
+ }
+ }
+
+ None
+ }
+
+ fn as_point(&self) -> Option<LayoutPoint> {
+ if self.is_badvalue() {
+ return None;
+ }
+
+ if let Some(nums) = self.as_vec_f32() {
+ if nums.len() == 2 {
+ return Some(LayoutPoint::new(nums[0], nums[1]));
+ }
+ }
+
+ None
+ }
+
+ fn as_vector(&self) -> Option<LayoutVector2D> {
+ self.as_point().map(|p| p.to_vector())
+ }
+
+ fn as_matrix4d(&self) -> Option<LayoutTransform> {
+ if let Some(nums) = self.as_vec_f32() {
+ assert_eq!(nums.len(), 16, "expected 16 floats, got '{:?}'", self);
+ Some(LayoutTransform::new(
+ nums[0], nums[1], nums[2], nums[3],
+ nums[4], nums[5], nums[6], nums[7],
+ nums[8], nums[9], nums[10], nums[11],
+ nums[12], nums[13], nums[14], nums[15],
+ ))
+ } else {
+ None
+ }
+ }
+
+ fn as_transform(&self, transform_origin: &LayoutPoint) -> Option<LayoutTransform> {
+ if let Some(transform) = self.as_matrix4d() {
+ return Some(transform);
+ }
+
+ match *self {
+ Yaml::String(ref string) => {
+ let mut slice = string.as_str();
+ let mut transform = LayoutTransform::identity();
+ while !slice.is_empty() {
+ let (function, ref args, reminder) = parse_function(slice);
+ slice = reminder;
+ let mx = match function {
+ "translate" if args.len() >= 2 => {
+ let z = args.get(2).and_then(|a| a.parse().ok()).unwrap_or(0.);
+ LayoutTransform::translation(
+ args[0].parse().unwrap(),
+ args[1].parse().unwrap(),
+ z,
+ )
+ }
+ "rotate" | "rotate-z" if args.len() == 1 => {
+ make_rotation(transform_origin, args[0].parse().unwrap(), 0.0, 0.0, 1.0)
+ }
+ "rotate-x" if args.len() == 1 => {
+ make_rotation(transform_origin, args[0].parse().unwrap(), 1.0, 0.0, 0.0)
+ }
+ "rotate-y" if args.len() == 1 => {
+ make_rotation(transform_origin, args[0].parse().unwrap(), 0.0, 1.0, 0.0)
+ }
+ "scale" if !args.is_empty() => {
+ let x = args[0].parse().unwrap();
+ // Default to uniform X/Y scale if Y unspecified.
+ let y = args.get(1).and_then(|a| a.parse().ok()).unwrap_or(x);
+ // Default to no Z scale if unspecified.
+ let z = args.get(2).and_then(|a| a.parse().ok()).unwrap_or(1.0);
+ LayoutTransform::scale(x, y, z)
+ }
+ "scale-x" if args.len() == 1 => {
+ LayoutTransform::scale(args[0].parse().unwrap(), 1.0, 1.0)
+ }
+ "scale-y" if args.len() == 1 => {
+ LayoutTransform::scale(1.0, args[0].parse().unwrap(), 1.0)
+ }
+ "scale-z" if args.len() == 1 => {
+ LayoutTransform::scale(1.0, 1.0, args[0].parse().unwrap())
+ }
+ "skew" if !args.is_empty() => {
+ // Default to no Y skew if unspecified.
+ let skew_y = args.get(1).and_then(|a| a.parse().ok()).unwrap_or(0.0);
+ make_skew(args[0].parse().unwrap(), skew_y)
+ }
+ "skew-x" if args.len() == 1 => {
+ make_skew(args[0].parse().unwrap(), 0.0)
+ }
+ "skew-y" if args.len() == 1 => {
+ make_skew(0.0, args[0].parse().unwrap())
+ }
+ "perspective" if args.len() == 1 => {
+ LayoutTransform::perspective(args[0].parse().unwrap())
+ }
+ _ => {
+ println!("unknown function {}", function);
+ break;
+ }
+ };
+ transform = transform.then(&mx);
+ }
+ Some(transform)
+ }
+ Yaml::Array(ref array) => {
+ let transform = array.iter().fold(
+ LayoutTransform::identity(),
+ |u, yaml| if let Some(transform) = yaml.as_transform(transform_origin) {
+ transform.then(&u)
+ } else {
+ u
+ },
+ );
+ Some(transform)
+ }
+ Yaml::BadValue => None,
+ _ => {
+ println!("unknown transform {:?}", self);
+ None
+ }
+ }
+ }
+
+ /// Inputs for r, g, b channels are floats or ints in the range [0, 255].
+ /// If included, the alpha channel is in the range [0, 1].
+ /// This matches CSS-style, but requires conversion for `ColorF`.
+ fn as_colorf(&self) -> Option<ColorF> {
+ if let Some(nums) = self.as_vec_f32() {
+ assert!(nums.iter().take(3).all(|x| (0.0 ..= 255.0).contains(x)),
+ "r, g, b values should be in the 0-255 range, got {:?}", nums);
+
+ let color: ColorF = match *nums.as_slice() {
+ [r, g, b] => ColorF { r, g, b, a: 1.0 },
+ [r, g, b, a] => ColorF { r, g, b, a },
+ _ => panic!("color expected a color name, or 3-4 floats; got '{:?}'", self),
+ }.scale_rgb(1.0 / 255.0);
+
+ assert!((0.0 ..= 1.0).contains(&color.a),
+ "alpha value should be in the 0-1 range, got {:?}",
+ color.a);
+
+ Some(color)
+ } else if let Some(s) = self.as_str() {
+ string_to_color(s)
+ } else {
+ None
+ }
+ }
+
+ fn as_vec_colorf(&self) -> Option<Vec<ColorF>> {
+ if let Some(v) = self.as_vec() {
+ Some(v.iter().map(|v| v.as_colorf().unwrap()).collect())
+ } else { self.as_colorf().map(|color| vec![color]) }
+ }
+
+ fn as_vec_string(&self) -> Option<Vec<String>> {
+ if let Some(v) = self.as_vec() {
+ Some(v.iter().map(|v| v.as_str().unwrap().to_owned()).collect())
+ } else { self.as_str().map(|s| vec![s.to_owned()]) }
+ }
+
+ fn as_border_radius_component(&self) -> LayoutSize {
+ if let Yaml::Integer(integer) = *self {
+ return LayoutSize::new(integer as f32, integer as f32);
+ }
+ self.as_size().unwrap_or_else(Size2D::zero)
+ }
+
+ fn as_border_radius(&self) -> Option<BorderRadius> {
+ if let Some(size) = self.as_size() {
+ return Some(BorderRadius::uniform_size(size));
+ }
+
+ match *self {
+ Yaml::BadValue => None,
+ Yaml::String(ref s) | Yaml::Real(ref s) => {
+ let fv = f32::from_str(s).unwrap();
+ Some(BorderRadius::uniform(fv))
+ }
+ Yaml::Integer(v) => Some(BorderRadius::uniform(v as f32)),
+ Yaml::Array(ref array) if array.len() == 4 => {
+ let top_left = array[0].as_border_radius_component();
+ let top_right = array[1].as_border_radius_component();
+ let bottom_left = array[2].as_border_radius_component();
+ let bottom_right = array[3].as_border_radius_component();
+ Some(BorderRadius {
+ top_left,
+ top_right,
+ bottom_left,
+ bottom_right,
+ })
+ }
+ Yaml::Hash(_) => {
+ let top_left = self["top-left"].as_border_radius_component();
+ let top_right = self["top-right"].as_border_radius_component();
+ let bottom_left = self["bottom-left"].as_border_radius_component();
+ let bottom_right = self["bottom-right"].as_border_radius_component();
+ Some(BorderRadius {
+ top_left,
+ top_right,
+ bottom_left,
+ bottom_right,
+ })
+ }
+ _ => {
+ panic!("Invalid border radius specified: {:?}", self);
+ }
+ }
+ }
+
+ fn as_transform_style(&self) -> Option<TransformStyle> {
+ self.as_str().and_then(StringEnum::from_str)
+ }
+
+ fn as_raster_space(&self) -> Option<RasterSpace> {
+ self.as_str().map(|s| {
+ match parse_function(s) {
+ ("screen", _, _) => {
+ RasterSpace::Screen
+ }
+ ("local", ref args, _) if args.len() == 1 => {
+ RasterSpace::Local(args[0].parse().unwrap())
+ }
+ f => {
+ panic!("error parsing raster space {:?}", f);
+ }
+ }
+ })
+ }
+
+ fn as_mix_blend_mode(&self) -> Option<MixBlendMode> {
+ self.as_str().and_then(StringEnum::from_str)
+ }
+
+ fn as_clip_mode(&self) -> Option<ClipMode> {
+ self.as_str().and_then(StringEnum::from_str)
+ }
+
+ fn as_filter_op(&self) -> Option<FilterOp> {
+ if let Some(s) = self.as_str() {
+ match parse_function(s) {
+ ("identity", _, _) => {
+ Some(FilterOp::Identity)
+ }
+ ("component-transfer", _, _) => {
+ Some(FilterOp::ComponentTransfer)
+ }
+ ("blur", ref args, _) if args.len() == 2 => {
+ Some(FilterOp::Blur(args[0].parse().unwrap(), args[1].parse().unwrap()))
+ }
+ ("brightness", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Brightness(args[0].parse().unwrap()))
+ }
+ ("contrast", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Contrast(args[0].parse().unwrap()))
+ }
+ ("grayscale", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Grayscale(args[0].parse().unwrap()))
+ }
+ ("hue-rotate", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::HueRotate(args[0].parse().unwrap()))
+ }
+ ("invert", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Invert(args[0].parse().unwrap()))
+ }
+ ("opacity", ref args, _) if args.len() == 1 => {
+ let amount: f32 = args[0].parse().unwrap();
+ Some(FilterOp::Opacity(amount.into(), amount))
+ }
+ ("saturate", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Saturate(args[0].parse().unwrap()))
+ }
+ ("sepia", ref args, _) if args.len() == 1 => {
+ Some(FilterOp::Sepia(args[0].parse().unwrap()))
+ }
+ ("srgb-to-linear", _, _) => Some(FilterOp::SrgbToLinear),
+ ("linear-to-srgb", _, _) => Some(FilterOp::LinearToSrgb),
+ ("drop-shadow", ref args, _) if args.len() == 3 => {
+ let str = format!("---\noffset: {}\nblur-radius: {}\ncolor: {}\n", args[0], args[1], args[2]);
+ let mut yaml_doc = YamlLoader::load_from_str(&str).expect("Failed to parse drop-shadow");
+ let yaml = yaml_doc.pop().unwrap();
+ Some(FilterOp::DropShadow(Shadow {
+ offset: yaml["offset"].as_vector().unwrap(),
+ blur_radius: yaml["blur-radius"].as_f32().unwrap(),
+ color: yaml["color"].as_colorf().unwrap()
+ }))
+ }
+ ("color-matrix", ref args, _) if args.len() == 20 => {
+ let m: Vec<f32> = args.iter().map(|f| f.parse().unwrap()).collect();
+ let mut matrix: [f32; 20] = [0.0; 20];
+ matrix.clone_from_slice(&m);
+ Some(FilterOp::ColorMatrix(matrix))
+ }
+ ("flood", ref args, _) if args.len() == 1 => {
+ let str = format!("---\ncolor: {}\n", args[0]);
+ let mut yaml_doc = YamlLoader::load_from_str(&str).expect("Failed to parse flood");
+ let yaml = yaml_doc.pop().unwrap();
+ Some(FilterOp::Flood(yaml["color"].as_colorf().unwrap()))
+ }
+ (_, _, _) => None,
+ }
+ } else {
+ None
+ }
+ }
+
+ fn as_vec_filter_op(&self) -> Option<Vec<FilterOp>> {
+ if let Some(v) = self.as_vec() {
+ Some(v.iter().map(|x| x.as_filter_op().unwrap()).collect())
+ } else {
+ self.as_filter_op().map(|op| vec![op])
+ }
+ }
+
+ fn as_filter_data(&self) -> Option<FilterData> {
+ // Parse an array with five entries. First entry is an array of func types (4).
+ // The remaining entries are arrays of floats.
+ if let Yaml::Array(ref array) = *self {
+ if array.len() != 5 {
+ panic!("Invalid filter data specified, base array doesn't have five entries: {:?}", self);
+ }
+ if let Some(func_types_p) = array[0].as_vec_string() {
+ if func_types_p.len() != 4 {
+ panic!("Invalid filter data specified, func type array doesn't have five entries: {:?}", self);
+ }
+ let func_types: Vec<ComponentTransferFuncType> =
+ func_types_p.into_iter().map(|x|
+ StringEnum::from_str(&x).unwrap_or_else(||
+ panic!("Invalid filter data specified, invalid func type name: {:?}", self))
+ ).collect();
+ if let Some(r_values_p) = array[1].as_vec_f32() {
+ if let Some(g_values_p) = array[2].as_vec_f32() {
+ if let Some(b_values_p) = array[3].as_vec_f32() {
+ if let Some(a_values_p) = array[4].as_vec_f32() {
+ let filter_data = FilterData {
+ func_r_type: func_types[0],
+ r_values: r_values_p,
+ func_g_type: func_types[1],
+ g_values: g_values_p,
+ func_b_type: func_types[2],
+ b_values: b_values_p,
+ func_a_type: func_types[3],
+ a_values: a_values_p,
+ };
+ return Some(filter_data)
+ }
+ }
+ }
+ }
+ }
+ }
+ None
+ }
+
+ fn as_filter_input(&self) -> Option<FilterPrimitiveInput> {
+ if let Some(input) = self.as_str() {
+ match input {
+ "original" => Some(FilterPrimitiveInput::Original),
+ "previous" => Some(FilterPrimitiveInput::Previous),
+ _ => None,
+ }
+ } else if let Some(index) = self.as_i64() {
+ if index >= 0 {
+ Some(FilterPrimitiveInput::OutputOfPrimitiveIndex(index as usize))
+ } else {
+ panic!("Filter input index cannot be negative");
+ }
+ } else {
+ panic!("Invalid filter input");
+ }
+ }
+
+ fn as_vec_filter_data(&self) -> Option<Vec<FilterData>> {
+ if let Some(v) = self.as_vec() {
+ Some(v.iter().map(|x| x.as_filter_data().unwrap()).collect())
+ } else {
+ self.as_filter_data().map(|data| vec![data])
+ }
+ }
+
+ fn as_filter_primitive(&self) -> Option<FilterPrimitive> {
+ if let Some(filter_type) = self["type"].as_str() {
+ let kind = match filter_type {
+ "identity" => {
+ FilterPrimitiveKind::Identity(IdentityPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ })
+ }
+ "blend" => {
+ FilterPrimitiveKind::Blend(BlendPrimitive {
+ input1: self["in1"].as_filter_input().unwrap(),
+ input2: self["in2"].as_filter_input().unwrap(),
+ mode: self["blend-mode"].as_mix_blend_mode().unwrap(),
+ })
+ }
+ "flood" => {
+ FilterPrimitiveKind::Flood(FloodPrimitive {
+ color: self["color"].as_colorf().unwrap(),
+ })
+ }
+ "blur" => {
+ FilterPrimitiveKind::Blur(BlurPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ width: self["width"].as_f32().unwrap(),
+ height: self["height"].as_f32().unwrap(),
+ })
+ }
+ "opacity" => {
+ FilterPrimitiveKind::Opacity(OpacityPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ opacity: self["opacity"].as_f32().unwrap(),
+ })
+ }
+ "color-matrix" => {
+ let m: Vec<f32> = self["matrix"].as_vec_f32().unwrap();
+ let mut matrix: [f32; 20] = [0.0; 20];
+ matrix.clone_from_slice(&m);
+
+ FilterPrimitiveKind::ColorMatrix(ColorMatrixPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ matrix,
+ })
+ }
+ "drop-shadow" => {
+ FilterPrimitiveKind::DropShadow(DropShadowPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ shadow: Shadow {
+ offset: self["offset"].as_vector().unwrap(),
+ color: self["color"].as_colorf().unwrap(),
+ blur_radius: self["radius"].as_f32().unwrap(),
+ }
+ })
+ }
+ "component-transfer" => {
+ FilterPrimitiveKind::ComponentTransfer(ComponentTransferPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ })
+ }
+ "offset" => {
+ FilterPrimitiveKind::Offset(OffsetPrimitive {
+ input: self["in"].as_filter_input().unwrap(),
+ offset: self["offset"].as_vector().unwrap(),
+ })
+ }
+ "composite" => {
+ let operator = match self["operator"].as_str().unwrap() {
+ "over" => CompositeOperator::Over,
+ "in" => CompositeOperator::In,
+ "out" => CompositeOperator::Out,
+ "atop" => CompositeOperator::Atop,
+ "xor" => CompositeOperator::Xor,
+ "lighter" => CompositeOperator::Lighter,
+ "arithmetic" => {
+ let k_vals = self["k-values"].as_vec_f32().unwrap();
+ assert!(k_vals.len() == 4, "Must be 4 k values for arithmetic composite operator");
+ let k_vals = [k_vals[0], k_vals[1], k_vals[2], k_vals[3]];
+ CompositeOperator::Arithmetic(k_vals)
+ }
+ _ => panic!("Invalid composite operator"),
+ };
+ FilterPrimitiveKind::Composite(CompositePrimitive {
+ input1: self["in1"].as_filter_input().unwrap(),
+ input2: self["in2"].as_filter_input().unwrap(),
+ operator,
+ })
+ }
+ _ => return None,
+ };
+
+ Some(FilterPrimitive {
+ kind,
+ color_space: self["color-space"].as_color_space().unwrap_or(ColorSpace::LinearRgb),
+ })
+ } else {
+ None
+ }
+ }
+
+ fn as_vec_filter_primitive(&self) -> Option<Vec<FilterPrimitive>> {
+ if let Some(v) = self.as_vec() {
+ Some(v.iter().map(|x| x.as_filter_primitive().unwrap()).collect())
+ } else {
+ self.as_filter_primitive().map(|data| vec![data])
+ }
+ }
+
+ fn as_color_space(&self) -> Option<ColorSpace> {
+ self.as_str().and_then(StringEnum::from_str)
+ }
+
+ fn as_complex_clip_region(&self) -> ComplexClipRegion {
+ let rect = self["rect"]
+ .as_rect()
+ .expect("Complex clip entry must have rect");
+ let radius = self["radius"]
+ .as_border_radius()
+ .unwrap_or_else(BorderRadius::zero);
+ let mode = self["clip-mode"]
+ .as_clip_mode()
+ .unwrap_or(ClipMode::Clip);
+ ComplexClipRegion::new(rect, radius, mode)
+ }
+
+ fn as_sticky_offset_bounds(&self) -> StickyOffsetBounds {
+ match *self {
+ Yaml::Array(ref array) => StickyOffsetBounds::new(
+ array[0].as_f32().unwrap_or(0.0),
+ array[1].as_f32().unwrap_or(0.0),
+ ),
+ _ => StickyOffsetBounds::new(0.0, 0.0),
+ }
+ }
+
+ fn as_gradient(&self, dl: &mut DisplayListBuilder) -> Gradient {
+ let start = self["start"].as_point().expect("gradient must have start");
+ let end = self["end"].as_point().expect("gradient must have end");
+ let stops = self["stops"]
+ .as_vec()
+ .expect("gradient must have stops")
+ .chunks(2)
+ .map(|chunk| {
+ GradientStop {
+ offset: chunk[0]
+ .as_force_f32()
+ .expect("gradient stop offset is not f32"),
+ color: chunk[1]
+ .as_colorf()
+ .expect("gradient stop color is not color"),
+ }
+ })
+ .collect::<Vec<_>>();
+ let extend_mode = if self["repeat"].as_bool().unwrap_or(false) {
+ ExtendMode::Repeat
+ } else {
+ ExtendMode::Clamp
+ };
+
+ dl.create_gradient(start, end, stops, extend_mode)
+ }
+
+ fn as_radial_gradient(&self, dl: &mut DisplayListBuilder) -> RadialGradient {
+ let center = self["center"].as_point().expect("radial gradient must have center");
+ let radius = self["radius"].as_size().expect("radial gradient must have a radius");
+ let stops = self["stops"]
+ .as_vec()
+ .expect("radial gradient must have stops")
+ .chunks(2)
+ .map(|chunk| {
+ GradientStop {
+ offset: chunk[0]
+ .as_force_f32()
+ .expect("gradient stop offset is not f32"),
+ color: chunk[1]
+ .as_colorf()
+ .expect("gradient stop color is not color"),
+ }
+ })
+ .collect::<Vec<_>>();
+ let extend_mode = if self["repeat"].as_bool().unwrap_or(false) {
+ ExtendMode::Repeat
+ } else {
+ ExtendMode::Clamp
+ };
+
+ dl.create_radial_gradient(center, radius, stops, extend_mode)
+ }
+
+ fn as_conic_gradient(&self, dl: &mut DisplayListBuilder) -> ConicGradient {
+ let center = self["center"].as_point().expect("conic gradient must have center");
+ let angle = self["angle"].as_force_f32().expect("conic gradient must have an angle");
+ let stops = self["stops"]
+ .as_vec()
+ .expect("conic gradient must have stops")
+ .chunks(2)
+ .map(|chunk| {
+ GradientStop {
+ offset: chunk[0]
+ .as_force_f32()
+ .expect("gradient stop offset is not f32"),
+ color: chunk[1]
+ .as_colorf()
+ .expect("gradient stop color is not color"),
+ }
+ })
+ .collect::<Vec<_>>();
+ let extend_mode = if self["repeat"].as_bool().unwrap_or(false) {
+ ExtendMode::Repeat
+ } else {
+ ExtendMode::Clamp
+ };
+
+ dl.create_conic_gradient(center, angle, stops, extend_mode)
+ }
+
+ fn as_complex_clip_regions(&self) -> Vec<ComplexClipRegion> {
+ match *self {
+ Yaml::Array(ref array) => array
+ .iter()
+ .map(Yaml::as_complex_clip_region)
+ .collect(),
+ Yaml::BadValue => vec![],
+ _ => {
+ println!("Unable to parse complex clip region {:?}", self);
+ vec![]
+ }
+ }
+ }
+
+ fn as_rotation(&self) -> Option<Rotation> {
+ match *self {
+ Yaml::Integer(0) => Some(Rotation::Degree0),
+ Yaml::Integer(90) => Some(Rotation::Degree90),
+ Yaml::Integer(180) => Some(Rotation::Degree180),
+ Yaml::Integer(270) => Some(Rotation::Degree270),
+ Yaml::BadValue => None,
+ _ => {
+ println!("Unable to parse rotation {:?}", self);
+ None
+ }
+ }
+ }
+}