1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 15:37:43 +00:00

LibWeb: Don't try to paint SVG elements transformed to zero size

Otherwise, the Gfx::Painter will get choked up on NaNs and start
infinitely splitting paths till it OOMs.
This commit is contained in:
MacDue 2023-04-11 00:18:28 +01:00 committed by Andreas Kling
parent ba7383d28f
commit ba6272a0a0
3 changed files with 18 additions and 11 deletions

View file

@ -25,7 +25,7 @@ CSSPixelPoint SVGGeometryBox::viewbox_origin() const
return { svg_box->view_box().value().min_x, svg_box->view_box().value().min_y }; return { svg_box->view_box().value().min_x, svg_box->view_box().value().min_y };
} }
Gfx::AffineTransform SVGGeometryBox::layout_transform() const Optional<Gfx::AffineTransform> SVGGeometryBox::layout_transform() const
{ {
auto& geometry_element = dom_node(); auto& geometry_element = dom_node();
auto transform = geometry_element.get_transform(); auto transform = geometry_element.get_transform();
@ -37,9 +37,12 @@ Gfx::AffineTransform SVGGeometryBox::layout_transform() const
// Note: SVGFormattingContext has already done the scaling based on the viewbox, // Note: SVGFormattingContext has already done the scaling based on the viewbox,
// we now have to derive what it was from the original bounding box size. // we now have to derive what it was from the original bounding box size.
// FIXME: It would be nice if we could store the transform from layout somewhere, so we don't have to solve for it here. // FIXME: It would be nice if we could store the transform from layout somewhere, so we don't have to solve for it here.
auto original_bounding_box = Gfx::AffineTransform {}.translate(-origin).multiply(transform).map(const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box());
// If the transform (or path) results in a empty box we can't display this.
if (original_bounding_box.is_empty())
return {};
auto scaled_width = paint_box()->content_width().value(); auto scaled_width = paint_box()->content_width().value();
auto scaled_height = paint_box()->content_height().value(); auto scaled_height = paint_box()->content_height().value();
auto original_bounding_box = Gfx::AffineTransform {}.translate(-origin).multiply(transform).map(const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box());
scaling = min(scaled_width / original_bounding_box.width(), scaled_height / original_bounding_box.height()); scaling = min(scaled_width / original_bounding_box.width(), scaled_height / original_bounding_box.height());
auto scaled_bounding_box = original_bounding_box.scaled(scaling, scaling); auto scaled_bounding_box = original_bounding_box.scaled(scaling, scaling);
paint_offset = (paint_box()->absolute_rect().location() - svg_box->paint_box()->absolute_rect().location()).to_type<float>() - scaled_bounding_box.location(); paint_offset = (paint_box()->absolute_rect().location() - svg_box->paint_box()->absolute_rect().location()).to_type<float>() - scaled_bounding_box.location();

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <AK/Optional.h>
#include <LibWeb/Layout/SVGGraphicsBox.h> #include <LibWeb/Layout/SVGGraphicsBox.h>
#include <LibWeb/SVG/SVGGeometryElement.h> #include <LibWeb/SVG/SVGGeometryElement.h>
@ -21,7 +22,7 @@ public:
SVG::SVGGeometryElement& dom_node() { return static_cast<SVG::SVGGeometryElement&>(SVGGraphicsBox::dom_node()); } SVG::SVGGeometryElement& dom_node() { return static_cast<SVG::SVGGeometryElement&>(SVGGraphicsBox::dom_node()); }
SVG::SVGGeometryElement const& dom_node() const { return static_cast<SVG::SVGGeometryElement const&>(SVGGraphicsBox::dom_node()); } SVG::SVGGeometryElement const& dom_node() const { return static_cast<SVG::SVGGeometryElement const&>(SVGGraphicsBox::dom_node()); }
Gfx::AffineTransform layout_transform() const; Optional<Gfx::AffineTransform> layout_transform() const;
virtual JS::GCPtr<Painting::Paintable> create_paintable() const override; virtual JS::GCPtr<Painting::Paintable> create_paintable() const override;

View file

@ -33,10 +33,12 @@ Optional<HitTestResult> SVGGeometryPaintable::hit_test(CSSPixelPoint position, H
if (!result.has_value()) if (!result.has_value())
return {}; return {};
auto& geometry_element = layout_box().dom_node(); auto& geometry_element = layout_box().dom_node();
auto transformed_bounding_box = layout_box().layout_transform().map_to_quad( if (auto transform = layout_box().layout_transform(); transform.has_value()) {
const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box()); auto transformed_bounding_box = transform->map_to_quad(
if (!transformed_bounding_box.contains(position.to_type<float>())) const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().bounding_box());
return {}; if (!transformed_bounding_box.contains(position.to_type<float>()))
return {};
}
return result; return result;
} }
@ -56,6 +58,7 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
auto& svg_context = context.svg_context(); auto& svg_context = context.svg_context();
// FIXME: This should not be trucated to an int. // FIXME: This should not be trucated to an int.
Gfx::PainterStateSaver save_painter { context.painter() };
auto offset = context.floored_device_point(svg_context.svg_element_position()).to_type<int>().to_type<float>(); auto offset = context.floored_device_point(svg_context.svg_element_position()).to_type<int>().to_type<float>();
painter.translate(offset); painter.translate(offset);
@ -65,7 +68,10 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
context.painter().add_clip_rect(context.enclosing_device_rect(absolute_rect()).to_type<int>()); context.painter().add_clip_rect(context.enclosing_device_rect(absolute_rect()).to_type<int>());
auto css_scale = context.device_pixels_per_css_pixel(); auto css_scale = context.device_pixels_per_css_pixel();
Gfx::Path path = const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().copy_transformed(Gfx::AffineTransform {}.scale(css_scale, css_scale).multiply(layout_box().layout_transform())); auto transform = layout_box().layout_transform();
if (!transform.has_value())
return;
Gfx::Path path = const_cast<SVG::SVGGeometryElement&>(geometry_element).get_path().copy_transformed(Gfx::AffineTransform {}.scale(css_scale, css_scale).multiply(*transform));
if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) { if (auto fill_color = geometry_element.fill_color().value_or(svg_context.fill_color()); fill_color.alpha() > 0) {
// We need to fill the path before applying the stroke, however the filled // We need to fill the path before applying the stroke, however the filled
@ -87,9 +93,6 @@ void SVGGeometryPaintable::paint(PaintContext& context, PaintPhase phase) const
stroke_color, stroke_color,
geometry_element.stroke_width().value_or(svg_context.stroke_width()) * context.device_pixels_per_css_pixel()); geometry_element.stroke_width().value_or(svg_context.stroke_width()) * context.device_pixels_per_css_pixel());
} }
painter.translate(-offset);
context.painter().clear_clip_rect();
} }
} }