diff options
Diffstat (limited to 'rust/crates/graphics/tests')
| -rw-r--r-- | rust/crates/graphics/tests/gradient_parser.rs | 375 | ||||
| -rw-r--r-- | rust/crates/graphics/tests/gradient_stops.rs | 176 | ||||
| -rw-r--r-- | rust/crates/graphics/tests/graphics_render.rs | 261 |
3 files changed, 812 insertions, 0 deletions
diff --git a/rust/crates/graphics/tests/gradient_parser.rs b/rust/crates/graphics/tests/gradient_parser.rs new file mode 100644 index 00000000..4e905ee9 --- /dev/null +++ b/rust/crates/graphics/tests/gradient_parser.rs @@ -0,0 +1,375 @@ +use graphics::gradient::ast::*; +use graphics::gradient::parser::parse_gradient; + +fn single(code: &str) -> GradientAst { + let ast = parse_gradient(code).unwrap_or_else(|err| panic!("failed to parse {code:?}: {err}")); + assert_eq!(ast.len(), 1, "expected one gradient in {code:?}"); + ast.into_iter().next().unwrap() +} + +#[test] +fn linear_minimal() { + let gradient = single("linear-gradient(red, blue)"); + assert_eq!(gradient.kind, GradientKind::Linear); + assert_eq!(gradient.orientation, None); + assert_eq!( + gradient.color_stops, + vec![ + ColorStop { color: Color::Literal("red".into()), length: None }, + ColorStop { color: Color::Literal("blue".into()), length: None }, + ] + ); +} + +#[test] +fn linear_directional() { + let gradient = single("linear-gradient(to right, #fff, #000)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "right".into() + ))) + ); + assert_eq!(gradient.color_stops[0].color, Color::Hex("fff".into())); +} + +#[test] +fn linear_corner() { + let gradient = single("linear-gradient(to left top, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "left top".into() + ))) + ); + let gradient = single("linear-gradient(to bottom right, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "bottom right".into() + ))) + ); +} + +#[test] +fn linear_angles() { + let gradient = single("linear-gradient(45deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "45".into() + ))) + ); + let gradient = single("linear-gradient(-90deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "-90".into() + ))) + ); + let gradient = single("linear-gradient(0.5rad, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + "0.5".into() + ))) + ); + let gradient = single("linear-gradient(.25deg, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Angular( + ".25".into() + ))) + ); +} + +#[test] +fn linear_legacy_direction() { + let gradient = single("linear-gradient(left, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Linear(LinearOrientation::Directional( + "left".into() + ))) + ); +} + +#[test] +fn repeating_and_vendor_prefix() { + assert_eq!( + single("repeating-linear-gradient(red, blue)").kind, + GradientKind::RepeatingLinear + ); + assert_eq!( + single("repeating-radial-gradient(red, blue)").kind, + GradientKind::RepeatingRadial + ); + assert_eq!( + single("-webkit-linear-gradient(red, blue)").kind, + GradientKind::Linear + ); + assert_eq!( + single("-moz-radial-gradient(red, blue)").kind, + GradientKind::Radial + ); + assert_eq!( + single("LINEAR-GRADIENT(red, blue)").kind, + GradientKind::Linear + ); +} + +#[test] +fn radial_shapes() { + let gradient = single("radial-gradient(circle, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { value: ShapeKind::Circle, style: None, at: None } + )])) + ); + + let gradient = single("radial-gradient(circle 50px, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Circle, + style: Some(ShapeStyle::Distance(Distance::Px("50".into()))), + at: None, + } + )])) + ); + + let gradient = single("radial-gradient(ellipse closest-side, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Ellipse, + style: Some(ShapeStyle::ExtentKeyword("closest-side".into())), + at: None, + } + )])) + ); +} + +#[test] +fn radial_with_position() { + let gradient = single("radial-gradient(circle at 25% 25%, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Circle, + style: None, + at: Some(Position { + x: Some(Distance::Percent("25".into())), + y: Some(Distance::Percent("25".into())), + }), + } + )])) + ); + + let gradient = single("radial-gradient(farthest-corner at 10px 2em, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![ + RadialOrientation::ExtentKeyword { + value: "farthest-corner".into(), + at: Some(Position { + x: Some(Distance::Px("10".into())), + y: Some(Distance::Em("2".into())), + }), + } + ])) + ); + + let gradient = single("radial-gradient(at center, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![ + RadialOrientation::DefaultRadial { + at: Position { + x: Some(Distance::PositionKeyword("center".into())), + y: None, + }, + } + ])) + ); + + let gradient = single("radial-gradient(100px 50px at 10% 20%, red, blue)"); + assert_eq!( + gradient.orientation, + Some(GradientOrientation::Radial(vec![RadialOrientation::Shape( + Shape { + value: ShapeKind::Ellipse, + style: Some(ShapeStyle::Position(Position { + x: Some(Distance::Px("100".into())), + y: Some(Distance::Px("50".into())), + })), + at: Some(Position { + x: Some(Distance::Percent("10".into())), + y: Some(Distance::Percent("20".into())), + }), + } + )])) + ); +} + +#[test] +fn radial_multiple_orientations() { + let gradient = single("radial-gradient(circle, ellipse, red, blue)"); + match gradient.orientation { + Some(GradientOrientation::Radial(list)) => assert_eq!(list.len(), 2), + other => panic!("unexpected orientation: {other:?}"), + } +} + +#[test] +fn color_formats() { + let gradient = single("linear-gradient(#abcdef, rgb(1,2,3), rgba(1,2,3,0.5), hsl(120,50%,50%), hsla(120,50%,50%,0.5), var(--brand), rebeccapurple)"); + assert_eq!(gradient.color_stops[0].color, Color::Hex("abcdef".into())); + assert_eq!( + gradient.color_stops[1].color, + Color::Rgb(vec!["1".into(), "2".into(), "3".into()]) + ); + assert_eq!( + gradient.color_stops[2].color, + Color::Rgba(vec!["1".into(), "2".into(), "3".into(), "0.5".into()]) + ); + assert_eq!( + gradient.color_stops[3].color, + Color::Hsl(["120".into(), "50".into(), "50".into()]) + ); + assert_eq!( + gradient.color_stops[4].color, + Color::Hsla(["120".into(), "50".into(), "50".into(), "0.5".into()]) + ); + assert_eq!(gradient.color_stops[5].color, Color::Var("--brand".into())); + assert_eq!( + gradient.color_stops[6].color, + Color::Literal("rebeccapurple".into()) + ); +} + +#[test] +fn stop_lengths() { + let gradient = single("linear-gradient(red 10%, blue 20px, green 3em, white calc(10% + 5px))"); + assert_eq!( + gradient.color_stops[0].length, + Some(Distance::Percent("10".into())) + ); + assert_eq!( + gradient.color_stops[1].length, + Some(Distance::Px("20".into())) + ); + assert_eq!( + gradient.color_stops[2].length, + Some(Distance::Em("3".into())) + ); + assert_eq!( + gradient.color_stops[3].length, + Some(Distance::Calc("10% + 5px".into())) + ); +} + +#[test] +fn multiple_gradients() { + let ast = parse_gradient("linear-gradient(red, blue), radial-gradient(circle, white, black)") + .unwrap(); + assert_eq!(ast.len(), 2); + assert_eq!(ast[0].kind, GradientKind::Linear); + assert_eq!(ast[1].kind, GradientKind::Radial); +} + +#[test] +fn trailing_semicolon_and_whitespace() { + let ast = parse_gradient(" linear-gradient(red, blue); ").unwrap(); + assert_eq!(ast.len(), 1); +} + +#[test] +fn empty_input_is_empty_list() { + assert_eq!(parse_gradient("").unwrap(), vec![]); +} + +#[test] +fn double_space_directional_quirk() { + let gradient = single("linear-gradient(to right, red, blue)"); + assert_eq!(gradient.orientation, None); + assert_eq!(gradient.color_stops[0].color, Color::Literal("to".into())); + assert_eq!( + gradient.color_stops[0].length, + Some(Distance::PositionKeyword("right".into())) + ); +} + +#[test] +fn error_missing_close_paren() { + let err = parse_gradient("linear-gradient(red, blue").unwrap_err(); + assert_eq!(err.message, "Missing )"); +} + +#[test] +fn error_extra_comma() { + let err = parse_gradient("linear-gradient(red,,blue)").unwrap_err(); + assert_eq!(err.message, "Expected color definition"); +} + +#[test] +fn error_missing_comma_before_color_stops() { + let err = parse_gradient("linear-gradient(to left red, blue)").unwrap_err(); + assert_eq!(err.message, "Missing comma before color stops"); +} + +#[test] +fn error_invalid_input_not_eof() { + let err = parse_gradient("foo(red)").unwrap_err(); + assert_eq!(err.message, "Invalid input not EOF"); + assert_eq!(err.input, "foo(red)"); +} + +#[test] +fn error_hsl_hue_percentage() { + let err = parse_gradient("linear-gradient(hsl(50%, 50%, 50%), red)").unwrap_err(); + assert!(err.message.starts_with("HSL hue value must be a number")); +} + +#[test] +fn error_hsl_missing_percentages() { + let err = parse_gradient("linear-gradient(hsl(120, 50, 50), red)").unwrap_err(); + assert_eq!( + err.message, + "Expected percentage value for saturation and lightness in HSL" + ); +} + +#[test] +fn error_rgb_empty() { + let err = parse_gradient("linear-gradient(rgb(), red)").unwrap_err(); + assert_eq!(err.message, "Expected number"); +} + +#[test] +fn error_calc_unbalanced() { + let err = parse_gradient("linear-gradient(red calc(10%").unwrap_err(); + assert_eq!(err.message, "Missing closing parenthesis in calc() expression"); +} + +#[test] +fn error_var_missing_name() { + let err = parse_gradient("linear-gradient(var(), red)").unwrap_err(); + assert_eq!(err.message, "Expected CSS variable name"); +} + +#[test] +fn error_at_missing_position() { + let err = parse_gradient("radial-gradient(circle at, red, blue)").unwrap_err(); + assert_eq!(err.message, "Missing positioning value"); +} + +#[test] +fn error_display_format() { + let err = parse_gradient("foo()").unwrap_err(); + assert_eq!(err.to_string(), "foo(): Invalid input not EOF"); +} diff --git a/rust/crates/graphics/tests/gradient_stops.rs b/rust/crates/graphics/tests/gradient_stops.rs new file mode 100644 index 00000000..04adc893 --- /dev/null +++ b/rust/crates/graphics/tests/gradient_stops.rs @@ -0,0 +1,176 @@ +use graphics::gradient::ast::{Color, ColorStop, Distance}; +use graphics::gradient::stops::{color_to_string, normalize_color_stops}; + +fn stop(color: Color, length: Option<Distance>) -> ColorStop { + ColorStop { color, length } +} + +fn literal(name: &str) -> Color { + Color::Literal(name.to_string()) +} + +#[test] +fn color_string_round_trip() { + assert_eq!(color_to_string(&Color::Hex("abc".into())), "#abc"); + assert_eq!(color_to_string(&literal("red")), "red"); + assert_eq!( + color_to_string(&Color::Rgb(vec!["1".into(), "2".into(), "3".into()])), + "rgb(1,2,3)" + ); + assert_eq!( + color_to_string(&Color::Rgba(vec!["1".into(), "2".into(), "3".into(), "0.5".into()])), + "rgba(1,2,3,0.5)" + ); + assert_eq!( + color_to_string(&Color::Hsl(["120".into(), "50".into(), "50".into()])), + "hsl(120,50,50)" + ); + assert_eq!( + color_to_string(&Color::Hsla(["120".into(), "50".into(), "50".into(), "0.5".into()])), + "hsla(120,50,50,0.5)" + ); + assert_eq!(color_to_string(&Color::Var("--x".into())), "var(--x)"); +} + +#[test] +fn evenly_distributed_stops() { + let stops = normalize_color_stops( + &[stop(literal("red"), None), stop(literal("blue"), None)], + 100.0, + ); + assert_eq!(stops[0].offset, 0.0); + assert_eq!(stops[1].offset, 1.0); + + let stops = normalize_color_stops( + &[ + stop(literal("a"), None), + stop(literal("b"), None), + stop(literal("c"), None), + ], + 100.0, + ); + assert_eq!(stops[1].offset, 0.5); +} + +#[test] +fn explicit_and_mixed_offsets() { + let stops = normalize_color_stops( + &[ + stop(literal("red"), Some(Distance::Percent("20".into()))), + stop(literal("green"), None), + stop(literal("blue"), Some(Distance::Percent("80".into()))), + ], + 100.0, + ); + assert_eq!(stops[0].offset, 0.2); + assert_eq!(stops[1].offset, 0.5); + assert_eq!(stops[2].offset, 0.8); +} + +#[test] +fn px_and_em_offsets_use_gradient_length() { + let stops = normalize_color_stops( + &[ + stop(literal("red"), Some(Distance::Px("50".into()))), + stop(literal("blue"), Some(Distance::Em("6.25".into()))), + ], + 200.0, + ); + assert_eq!(stops[0].offset, 0.25); + assert!((stops[1].offset - 0.5).abs() < 1e-9); +} + +#[test] +fn offsets_clamped() { + let stops = normalize_color_stops( + &[ + stop(literal("red"), Some(Distance::Percent("-20".into()))), + stop(literal("blue"), Some(Distance::Percent("150".into()))), + ], + 100.0, + ); + assert_eq!(stops[0].offset, 0.0); + assert_eq!(stops[1].offset, 1.0); +} + +#[test] +fn leading_unpositioned_stops() { + let stops = normalize_color_stops( + &[ + stop(literal("a"), None), + stop(literal("b"), None), + stop(literal("c"), Some(Distance::Percent("90".into()))), + ], + 100.0, + ); + assert_eq!(stops[0].offset, 0.0); + assert_eq!(stops[1].offset, 0.45); + assert_eq!(stops[2].offset, 0.9); +} + +#[test] +fn trailing_unpositioned_stops() { + let stops = normalize_color_stops( + &[ + stop(literal("a"), Some(Distance::Percent("10".into()))), + stop(literal("b"), None), + stop(literal("c"), None), + ], + 100.0, + ); + assert_eq!(stops[0].offset, 0.1); + assert!((stops[1].offset - 0.55).abs() < 1e-9); + assert_eq!(stops[2].offset, 1.0); +} + +#[test] +fn transparent_stop_adopts_previous_color() { + let stops = normalize_color_stops( + &[ + stop(Color::Hex("ff0000".into()), None), + stop(literal("transparent"), None), + stop(Color::Hex("0000ff".into()), None), + ], + 100.0, + ); + assert_eq!(stops[1].color, "rgba(255,0,0,0)"); +} + +#[test] +fn transparent_stop_with_unparseable_donors_unchanged() { + let stops = normalize_color_stops( + &[ + stop(literal("red"), None), + stop(literal("transparent"), None), + stop(literal("blue"), None), + ], + 100.0, + ); + assert_eq!(stops[1].color, "transparent"); +} + +#[test] +fn transparent_stop_adopts_next_color_when_first() { + let stops = normalize_color_stops( + &[ + stop(literal("transparent"), None), + stop(Color::Hex("00ff00".into()), None), + ], + 100.0, + ); + assert_eq!(stops[1].color, "#00ff00"); + assert_eq!(stops[0].color, "rgba(0,255,0,0)"); +} + +#[test] +fn all_transparent_stops_unchanged() { + let stops = normalize_color_stops( + &[ + stop(literal("transparent"), None), + stop(Color::Rgba(vec!["0".into(), "0".into(), "0".into(), "0".into()]), None), + ], + 100.0, + ); + assert_eq!(stops[0].color, "transparent"); + assert_eq!(stops[1].color, "rgba(0,0,0,0)"); +} diff --git a/rust/crates/graphics/tests/graphics_render.rs b/rust/crates/graphics/tests/graphics_render.rs new file mode 100644 index 00000000..a72f7fc9 --- /dev/null +++ b/rust/crates/graphics/tests/graphics_render.rs @@ -0,0 +1,261 @@ +use graphics::color::{hsl_to_rgb, is_transparent, parse_color_to_rgb, parse_css_color}; +use graphics::{ + GraphicsError, ParamValue, ParamValues, build_default_graphic_instance, draw_css_background, + get_graphic_definition, graphics_registry, parse_background_layers, render_graphic, + resolve_graphic_params, split_css_layers, BackgroundLayer, GradientKind, +}; +use tiny_skia::Pixmap; + +fn pixel(pixmap: &Pixmap, x: u32, y: u32) -> (u8, u8, u8, u8) { + let px = pixmap.pixel(x, y).unwrap(); + (px.red(), px.green(), px.blue(), px.alpha()) +} + +fn params(entries: &[(&str, ParamValue)]) -> ParamValues { + entries + .iter() + .map(|(key, value)| (key.to_string(), value.clone())) + .collect() +} + +#[test] +fn registry_lookup() { + let registry = graphics_registry(); + assert!(registry.has("rectangle")); + assert!(registry.has("ellipse")); + assert!(registry.has("polygon")); + assert!(registry.has("star")); + assert_eq!(registry.get_all().len(), 4); + + let rectangle = get_graphic_definition("rectangle").unwrap(); + assert_eq!(rectangle.name, "Rectangle"); + assert!(rectangle.keywords.contains(&"box")); + + let err = match get_graphic_definition("nope") { + Ok(_) => panic!("expected unknown graphic error"), + Err(err) => err, + }; + assert_eq!(err, GraphicsError::UnknownGraphic("nope".to_string())); + assert_eq!(err.to_string(), "Unknown graphic: nope"); +} + +#[test] +fn default_instance_and_resolve() { + let instance = build_default_graphic_instance("polygon").unwrap(); + assert_eq!(instance.definition_id, "polygon"); + assert_eq!(instance.params.get("sides"), Some(&ParamValue::Number(5.0))); + assert_eq!( + instance.params.get("fill"), + Some(&ParamValue::String("#ffffff".to_string())) + ); + assert_eq!( + instance.params.get("strokeAlign"), + Some(&ParamValue::String("center".to_string())) + ); + + let definition = get_graphic_definition("star").unwrap(); + let resolved = resolve_graphic_params( + definition, + Some(¶ms(&[("depth", ParamValue::Number(30.0))])), + ); + assert_eq!(resolved.get("depth"), Some(&ParamValue::Number(30.0))); + assert_eq!(resolved.get("points"), Some(&ParamValue::Number(5.0))); + + let defaults = resolve_graphic_params(definition, None); + assert_eq!(defaults.get("depth"), Some(&ParamValue::Number(45.0))); +} + +#[test] +fn color_parsing() { + let red = parse_css_color("#ff0000").unwrap(); + assert_eq!((red.r, red.g, red.b, red.a), (1.0, 0.0, 0.0, 1.0)); + let short = parse_css_color("#f00").unwrap(); + assert_eq!(short, red); + let with_alpha = parse_css_color("#ff000080").unwrap(); + assert!((with_alpha.a - 128.0 / 255.0).abs() < 1e-9); + let named = parse_css_color("rebeccapurple").unwrap(); + assert!((named.r - 102.0 / 255.0).abs() < 1e-9); + assert_eq!(parse_css_color("transparent").unwrap().a, 0.0); + let rgb = parse_css_color("rgb(255, 0, 0)").unwrap(); + assert_eq!(rgb, red); + let rgb_percent = parse_css_color("rgb(100%, 0%, 0%)").unwrap(); + assert!((rgb_percent.r - 1.0).abs() < 1e-9); + let rgba = parse_css_color("rgba(0, 255, 0, 0.5)").unwrap(); + assert_eq!((rgba.g, rgba.a), (1.0, 0.5)); + let hsl = parse_css_color("hsl(120, 100%, 50%)").unwrap(); + assert!((hsl.g - 1.0).abs() < 1e-9 && hsl.r.abs() < 1e-9); + assert!(parse_css_color("not-a-color").is_none()); + + let (r, g, b) = hsl_to_rgb(0.0, 1.0, 0.5); + assert_eq!((r, g, b), (1.0, 0.0, 0.0)); + + assert!(is_transparent("transparent")); + assert!(is_transparent("rgba(1, 2, 3, 0)")); + assert!(!is_transparent("rgba(1, 2, 3, 0.5)")); + assert!(!is_transparent("#00000000")); + + let parsed = parse_color_to_rgb("#ff0000").unwrap(); + assert_eq!((parsed.r, parsed.g, parsed.b), (255.0, 0.0, 0.0)); + assert!(parse_color_to_rgb("#ff00").is_none()); + assert!(parse_color_to_rgb("hsl(0,100%,50%)").is_none()); +} + +#[test] +fn layer_splitting() { + assert_eq!( + split_css_layers("#00ff00, linear-gradient(red, blue), rgba(1,2,3,0.5)"), + vec!["#00ff00", "linear-gradient(red, blue)", "rgba(1,2,3,0.5)"] + ); + + let layers = parse_background_layers("linear-gradient(red, blue)"); + assert!(matches!( + &layers[0], + BackgroundLayer::Gradient(ast) if ast.kind == GradientKind::Linear + )); + + let layers = parse_background_layers("rebeccapurple"); + assert_eq!(layers, vec![BackgroundLayer::Color("rebeccapurple".to_string())]); +} + +#[test] +fn rectangle_render_regions() { + let pixmap = render_graphic("rectangle", None, 64, 64).unwrap(); + assert_eq!(pixmap.width(), 64); + assert_eq!(pixmap.height(), 64); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (255, 255, 255, 255)); + + let rounded = render_graphic( + "rectangle", + Some(¶ms(&[("cornerRadius", ParamValue::Number(50.0))])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&rounded, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&rounded, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn ellipse_render_regions() { + let pixmap = render_graphic("ellipse", None, 64, 64).unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); + assert_eq!(pixel(&pixmap, 63, 63), (0, 0, 0, 0)); +} + +#[test] +fn star_render_regions() { + let pixmap = render_graphic("star", None, 64, 64).unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn polygon_render_regions() { + let pixmap = render_graphic( + "polygon", + Some(¶ms(&[("sides", ParamValue::Number(6.0))])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 0, 0), (0, 0, 0, 0)); +} + +#[test] +fn center_stroke_regions() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("center".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 2, 32), (0, 0, 0, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn inside_stroke_regions() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("inside".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 4, 32), (0, 0, 0, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn outside_stroke_has_no_inner_ring() { + let pixmap = render_graphic( + "rectangle", + Some(¶ms(&[ + ("strokeWidth", ParamValue::Number(10.0)), + ("strokeAlign", ParamValue::String("outside".to_string())), + ])), + 64, + 64, + ) + .unwrap(); + assert_eq!(pixel(&pixmap, 12, 32), (255, 255, 255, 255)); + assert_eq!(pixel(&pixmap, 32, 32), (255, 255, 255, 255)); +} + +#[test] +fn invalid_fill_color_errors() { + let err = render_graphic( + "rectangle", + Some(¶ms(&[("fill", ParamValue::String("bogus".to_string()))])), + 8, + 8, + ) + .unwrap_err(); + assert!(matches!(err, GraphicsError::InvalidColor(_))); +} + +#[test] +fn gradient_background_linear() { + let mut pixmap = Pixmap::new(64, 64).unwrap(); + draw_css_background(&mut pixmap, "linear-gradient(to right, #ff0000, #0000ff)").unwrap(); + let left = pixel(&pixmap, 1, 32); + let right = pixel(&pixmap, 62, 32); + assert!(left.0 > 200 && left.2 < 60, "left was {left:?}"); + assert!(right.2 > 200 && right.0 < 60, "right was {right:?}"); + assert_eq!(left.3, 255); + assert_eq!(right.3, 255); +} + +#[test] +fn gradient_background_radial() { + let mut pixmap = Pixmap::new(64, 64).unwrap(); + draw_css_background(&mut pixmap, "radial-gradient(circle, #ffffff, #000000)").unwrap(); + let center = pixel(&pixmap, 32, 32); + assert!(center.0 > 200, "center was {center:?}"); + let corner = pixel(&pixmap, 0, 0); + assert!(corner.0 < 60, "corner was {corner:?}"); +} + +#[test] +fn background_layer_paint_order() { + let mut pixmap = Pixmap::new(16, 16).unwrap(); + draw_css_background(&mut pixmap, "#00ff00, linear-gradient(#ff0000, #0000ff)").unwrap(); + assert_eq!(pixel(&pixmap, 8, 8), (0, 255, 0, 255)); + + let mut pixmap = Pixmap::new(16, 16).unwrap(); + draw_css_background(&mut pixmap, "rgba(255,0,0,0.5)").unwrap(); + let px = pixel(&pixmap, 8, 8); + assert!(px.3 > 120 && px.3 < 135, "alpha was {px:?}"); + assert!(px.0 > 120 && px.0 < 135, "premultiplied red was {px:?}"); + assert_eq!(px.1, 0); +} |
