diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs index 8e47f0b285..078692897c 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs @@ -3095,8 +3095,7 @@ pub(super) fn svg_geometry_get_total_length_callback<'s>( ) { let length = svg_geometry_segments(scope, args.this()) .iter() - .map(SvgGeometrySegment::length) - .sum::(); + .fold(0.0, |total, segment| total + segment.length()); rv.set(v8::Number::new(scope, length).into()); } diff --git a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs index e4d5b0d187..2c15c86278 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_elements/live_document.rs @@ -2728,6 +2728,47 @@ fn svg_geometry_queries_use_computed_paths_live_tree_and_kurbo_bounds() { ); } +#[test] +fn svg_path_errors_keep_valid_prefix_and_positive_zero_length() { + let mut vm = new_parsed_test_vm( + "https://svg-path-error-handling.test/", + r##" + + + + + + "##, + ); + + let result = vm + .eval( + r#" + (() => { + const byId = id => document.getElementById(id); + const invalid = byId("invalid"); + const end = invalid.getPointAtLength(invalid.getTotalLength()); + return JSON.stringify({ + length: invalid.getTotalLength(), + end: [end.x, end.y], + emptyIsPositiveZero: Object.is(byId("empty").getTotalLength(), 0), + noneIsPositiveZero: Object.is(byId("none").getTotalLength(), 0), + missingMoveIsPositiveZero: Object.is( + byId("missing_move").getTotalLength(), + 0, + ), + }); + })() + "#, + ) + .expect("SVG path error handling probe should evaluate"); + + assert_eq!( + result, + r#"{"length":20,"end":[30,10],"emptyIsPositiveZero":true,"noneIsPositiveZero":true,"missingMoveIsPositiveZero":true}"#, + ); +} + #[test] fn element_methods_and_dataset_live_on_owner_prototypes() { let mut vm = new_storage_test_vm("https://example.com/"); diff --git a/moli-svg/src/lib.rs b/moli-svg/src/lib.rs index 36898f43de..90d7db83a1 100644 --- a/moli-svg/src/lib.rs +++ b/moli-svg/src/lib.rs @@ -81,6 +81,17 @@ mod tests { assert_close(point.y, -20.0); } + #[test] + fn path_geometry_preserves_complete_segments_before_a_parse_error() { + let invalid_command = path_segments("M 10 10 L 30 10 X 50 10"); + assert_eq!(invalid_command.len(), 1); + assert_close(invalid_command[0].length(), 20.0); + + let incomplete_curve = path_segments("M 10 10 L 30 10 C 40 0 50 20"); + assert_eq!(incomplete_curve.len(), 1); + assert_close(incomplete_curve[0].length(), 20.0); + } + #[test] fn path_geometry_handles_relative_commands_and_close_path() { let segments = path_segments("m 1 1 l 3 0 v 4 h -3 z"); diff --git a/moli-svg/src/path.rs b/moli-svg/src/path.rs index e1f5caf9f3..452f101f0c 100644 --- a/moli-svg/src/path.rs +++ b/moli-svg/src/path.rs @@ -4,7 +4,10 @@ use svgtypes::{SimplePathSegment, SimplifyingPathParser}; pub(crate) fn path_geometry(raw: &str) -> Option { let mut path = BezPath::new(); for segment in SimplifyingPathParser::from(raw) { - match segment.ok()? { + let Ok(segment) = segment else { + break; + }; + match segment { SimplePathSegment::MoveTo { x, y } => path.move_to((x, y)), SimplePathSegment::LineTo { x, y } => path.line_to((x, y)), SimplePathSegment::CurveTo {