mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-19 15:32:31 +00:00
LibWeb: Don't allow SVG boxes to create a stacking context
Prior to this change, SVGs were following the CSS painting order, which means SVG boxes could have established stacking context and be sorted by z-index. There is a section in the spec that defines what kind of SVG boxes should create a stacking context https://www.w3.org/TR/SVG2/render.html#EstablishingStackingContex Although this spec is marked as a draft and rendering order described in this spec does not match what other engines do. This spec issue comment has a good summary of what other engines actually do regarding painting order https://github.com/w3c/svgwg/issues/264#issuecomment-246432360 "as long as you're relying solely on the default z-index (which SVG1 does, by definition), nothing ever changes order when you apply opacity/filter/etc". This change aligns our implementation with other engines by forbidding SVGs to create a formatting context and painting them in order they are defined in tree tree.
This commit is contained in:
parent
b645e26e9b
commit
83b6bc4ccb
Notes:
github-actions[bot]
2024-10-09 16:43:22 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: 83b6bc4ccb
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1697
17 changed files with 296 additions and 71 deletions
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rect1 {
|
||||||
|
fill: red;
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#rect2 {
|
||||||
|
fill: blue;
|
||||||
|
transform: translate(100px, 100px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<svg width="400" height="400">
|
||||||
|
<rect id="rect1" width="200" height="200"></rect>
|
||||||
|
<rect id="rect2" width="200" height="200"></rect>
|
||||||
|
</svg>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<link rel="match" href="reference/z-index-should-not-affect-svg-painting-order-ref.html" />
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rect1 {
|
||||||
|
fill: red;
|
||||||
|
transform: translate(50px, 50px);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rect2 {
|
||||||
|
fill: blue;
|
||||||
|
transform: translate(100px, 100px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<svg width="400" height="400">
|
||||||
|
<rect id="rect1" width="200" height="200"></rect>
|
||||||
|
<rect id="rect2" width="200" height="200"></rect>
|
||||||
|
</svg>
|
Binary file not shown.
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.9 KiB |
|
@ -152,6 +152,9 @@ bool Node::establishes_stacking_context() const
|
||||||
if (!has_style())
|
if (!has_style())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (is_svg_box() || is_svg_svg_box())
|
||||||
|
return false;
|
||||||
|
|
||||||
// We make a stacking context for the viewport. Painting and hit testing starts from here.
|
// We make a stacking context for the viewport. Painting and hit testing starts from here.
|
||||||
if (is_viewport())
|
if (is_viewport())
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -105,11 +105,6 @@ struct StackingContextTransform {
|
||||||
Gfx::FloatMatrix4x4 matrix;
|
Gfx::FloatMatrix4x4 matrix;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StackingContextMask {
|
|
||||||
NonnullRefPtr<Gfx::Bitmap> mask_bitmap;
|
|
||||||
Gfx::Bitmap::MaskKind mask_kind;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PushStackingContext {
|
struct PushStackingContext {
|
||||||
float opacity;
|
float opacity;
|
||||||
// The bounding box of the source paintable (pre-transform).
|
// The bounding box of the source paintable (pre-transform).
|
||||||
|
@ -117,7 +112,6 @@ struct PushStackingContext {
|
||||||
// A translation to be applied after the stacking context has been transformed.
|
// A translation to be applied after the stacking context has been transformed.
|
||||||
Gfx::IntPoint post_transform_translation;
|
Gfx::IntPoint post_transform_translation;
|
||||||
StackingContextTransform transform;
|
StackingContextTransform transform;
|
||||||
Optional<StackingContextMask> mask = {};
|
|
||||||
Optional<Gfx::Path> clip_path = {};
|
Optional<Gfx::Path> clip_path = {};
|
||||||
|
|
||||||
void translate_by(Gfx::IntPoint const& offset)
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
@ -387,6 +381,32 @@ struct PaintScrollBar {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ApplyOpacity {
|
||||||
|
float opacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApplyTransform {
|
||||||
|
Gfx::FloatPoint origin;
|
||||||
|
Gfx::FloatMatrix4x4 matrix;
|
||||||
|
Gfx::IntPoint post_transform_translation;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
origin.translate_by(offset.to_type<float>());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApplyMaskBitmap {
|
||||||
|
Gfx::IntPoint origin;
|
||||||
|
NonnullRefPtr<Gfx::Bitmap> bitmap;
|
||||||
|
Gfx::Bitmap::MaskKind kind;
|
||||||
|
|
||||||
|
void translate_by(Gfx::IntPoint const& offset)
|
||||||
|
{
|
||||||
|
origin.translate_by(offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using Command = Variant<
|
using Command = Variant<
|
||||||
DrawGlyphRun,
|
DrawGlyphRun,
|
||||||
FillRect,
|
FillRect,
|
||||||
|
@ -418,6 +438,9 @@ using Command = Variant<
|
||||||
AddRoundedRectClip,
|
AddRoundedRectClip,
|
||||||
AddMask,
|
AddMask,
|
||||||
PaintNestedDisplayList,
|
PaintNestedDisplayList,
|
||||||
PaintScrollBar>;
|
PaintScrollBar,
|
||||||
|
ApplyOpacity,
|
||||||
|
ApplyTransform,
|
||||||
|
ApplyMaskBitmap>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,6 +99,9 @@ void DisplayListPlayer::execute(DisplayList& display_list)
|
||||||
else HANDLE_COMMAND(AddMask, add_mask)
|
else HANDLE_COMMAND(AddMask, add_mask)
|
||||||
else HANDLE_COMMAND(PaintScrollBar, paint_scrollbar)
|
else HANDLE_COMMAND(PaintScrollBar, paint_scrollbar)
|
||||||
else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list)
|
else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list)
|
||||||
|
else HANDLE_COMMAND(ApplyOpacity, apply_opacity)
|
||||||
|
else HANDLE_COMMAND(ApplyTransform, apply_transform)
|
||||||
|
else HANDLE_COMMAND(ApplyMaskBitmap, apply_mask_bitmap)
|
||||||
else VERIFY_NOT_REACHED();
|
else VERIFY_NOT_REACHED();
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,9 @@ private:
|
||||||
virtual void add_mask(AddMask const&) = 0;
|
virtual void add_mask(AddMask const&) = 0;
|
||||||
virtual void paint_nested_display_list(PaintNestedDisplayList const&) = 0;
|
virtual void paint_nested_display_list(PaintNestedDisplayList const&) = 0;
|
||||||
virtual void paint_scrollbar(PaintScrollBar const&) = 0;
|
virtual void paint_scrollbar(PaintScrollBar const&) = 0;
|
||||||
|
virtual void apply_opacity(ApplyOpacity const&) = 0;
|
||||||
|
virtual void apply_transform(ApplyTransform const&) = 0;
|
||||||
|
virtual void apply_mask_bitmap(ApplyMaskBitmap const&) = 0;
|
||||||
virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0;
|
virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -452,47 +452,6 @@ void DisplayListPlayerSkia::push_stacking_context(PushStackingContext const& com
|
||||||
canvas.clipPath(to_skia_path(command.clip_path.value()), true);
|
canvas.clipPath(to_skia_path(command.clip_path.value()), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.mask.has_value()) {
|
|
||||||
auto sk_bitmap = to_skia_bitmap(*command.mask.value().mask_bitmap);
|
|
||||||
auto mask_image = SkImages::RasterFromBitmap(sk_bitmap);
|
|
||||||
|
|
||||||
char const* sksl_shader = nullptr;
|
|
||||||
if (command.mask->mask_kind == Gfx::Bitmap::MaskKind::Luminance) {
|
|
||||||
sksl_shader = R"(
|
|
||||||
uniform shader mask_image;
|
|
||||||
half4 main(float2 coord) {
|
|
||||||
half4 color = mask_image.eval(coord);
|
|
||||||
half luminance = 0.2126 * color.b + 0.7152 * color.g + 0.0722 * color.r;
|
|
||||||
return half4(0.0, 0.0, 0.0, color.a * luminance);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
} else if (command.mask->mask_kind == Gfx::Bitmap::MaskKind::Alpha) {
|
|
||||||
sksl_shader = R"(
|
|
||||||
uniform shader mask_image;
|
|
||||||
half4 main(float2 coord) {
|
|
||||||
half4 color = mask_image.eval(coord);
|
|
||||||
return half4(0.0, 0.0, 0.0, color.a);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
} else {
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl_shader));
|
|
||||||
if (!effect) {
|
|
||||||
dbgln("SkSL error: {}", error.c_str());
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
SkMatrix mask_matrix;
|
|
||||||
auto mask_position = command.source_paintable_rect.location();
|
|
||||||
mask_matrix.setTranslate(mask_position.x(), mask_position.y());
|
|
||||||
|
|
||||||
SkRuntimeShaderBuilder builder(effect);
|
|
||||||
builder.child("mask_image") = mask_image->makeShader(SkSamplingOptions(), mask_matrix);
|
|
||||||
canvas.clipShader(builder.makeShader());
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.concat(matrix);
|
canvas.concat(matrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1283,6 +1242,70 @@ void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command)
|
||||||
canvas.drawRRect(rrect, stroke_paint);
|
canvas.drawRRect(rrect, stroke_paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayListPlayerSkia::apply_opacity(ApplyOpacity const& command)
|
||||||
|
{
|
||||||
|
auto& canvas = surface().canvas();
|
||||||
|
SkPaint paint;
|
||||||
|
paint.setAlphaf(command.opacity);
|
||||||
|
canvas.saveLayer(nullptr, &paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayListPlayerSkia::apply_transform(ApplyTransform const& command)
|
||||||
|
{
|
||||||
|
auto affine_transform = Gfx::extract_2d_affine_transform(command.matrix);
|
||||||
|
auto new_transform = Gfx::AffineTransform {}
|
||||||
|
.set_translation(command.post_transform_translation.to_type<float>())
|
||||||
|
.translate(command.origin)
|
||||||
|
.multiply(affine_transform)
|
||||||
|
.translate(-command.origin);
|
||||||
|
auto matrix = to_skia_matrix(new_transform);
|
||||||
|
surface().canvas().concat(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayListPlayerSkia::apply_mask_bitmap(ApplyMaskBitmap const& command)
|
||||||
|
{
|
||||||
|
auto& canvas = surface().canvas();
|
||||||
|
|
||||||
|
auto sk_bitmap = to_skia_bitmap(*command.bitmap);
|
||||||
|
auto mask_image = SkImages::RasterFromBitmap(sk_bitmap);
|
||||||
|
|
||||||
|
char const* sksl_shader = nullptr;
|
||||||
|
if (command.kind == Gfx::Bitmap::MaskKind::Luminance) {
|
||||||
|
sksl_shader = R"(
|
||||||
|
uniform shader mask_image;
|
||||||
|
half4 main(float2 coord) {
|
||||||
|
half4 color = mask_image.eval(coord);
|
||||||
|
half luminance = 0.2126 * color.b + 0.7152 * color.g + 0.0722 * color.r;
|
||||||
|
return half4(0.0, 0.0, 0.0, color.a * luminance);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
} else if (command.kind == Gfx::Bitmap::MaskKind::Alpha) {
|
||||||
|
sksl_shader = R"(
|
||||||
|
uniform shader mask_image;
|
||||||
|
half4 main(float2 coord) {
|
||||||
|
half4 color = mask_image.eval(coord);
|
||||||
|
return half4(0.0, 0.0, 0.0, color.a);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
} else {
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [effect, error] = SkRuntimeEffect::MakeForShader(SkString(sksl_shader));
|
||||||
|
if (!effect) {
|
||||||
|
dbgln("SkSL error: {}", error.c_str());
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
SkMatrix mask_matrix;
|
||||||
|
auto mask_position = command.origin;
|
||||||
|
mask_matrix.setTranslate(mask_position.x(), mask_position.y());
|
||||||
|
|
||||||
|
SkRuntimeShaderBuilder builder(effect);
|
||||||
|
builder.child("mask_image") = mask_image->makeShader(SkSamplingOptions(), mask_matrix);
|
||||||
|
canvas.clipShader(builder.makeShader());
|
||||||
|
}
|
||||||
|
|
||||||
bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
|
bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
|
||||||
{
|
{
|
||||||
return surface().canvas().quickReject(to_skia_rect(rect));
|
return surface().canvas().quickReject(to_skia_rect(rect));
|
||||||
|
|
|
@ -79,6 +79,9 @@ private:
|
||||||
void add_mask(AddMask const&) override;
|
void add_mask(AddMask const&) override;
|
||||||
void paint_scrollbar(PaintScrollBar const&) override;
|
void paint_scrollbar(PaintScrollBar const&) override;
|
||||||
void paint_nested_display_list(PaintNestedDisplayList const&) override;
|
void paint_nested_display_list(PaintNestedDisplayList const&) override;
|
||||||
|
void apply_opacity(ApplyOpacity const&) override;
|
||||||
|
void apply_transform(ApplyTransform const&) override;
|
||||||
|
void apply_mask_bitmap(ApplyMaskBitmap const&) override;
|
||||||
|
|
||||||
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
|
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;
|
||||||
|
|
||||||
|
|
|
@ -306,7 +306,6 @@ void DisplayListRecorder::push_stacking_context(PushStackingContextParams params
|
||||||
.origin = params.transform.origin,
|
.origin = params.transform.origin,
|
||||||
.matrix = params.transform.matrix,
|
.matrix = params.transform.matrix,
|
||||||
},
|
},
|
||||||
.mask = params.mask,
|
|
||||||
.clip_path = params.clip_path });
|
.clip_path = params.clip_path });
|
||||||
m_state_stack.append(State());
|
m_state_stack.append(State());
|
||||||
}
|
}
|
||||||
|
@ -410,4 +409,27 @@ void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect rect
|
||||||
.vertical = vertical });
|
.vertical = vertical });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayListRecorder::apply_opacity(float opacity)
|
||||||
|
{
|
||||||
|
append(ApplyOpacity { .opacity = opacity });
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayListRecorder::apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4 matrix)
|
||||||
|
{
|
||||||
|
append(ApplyTransform {
|
||||||
|
.origin = origin,
|
||||||
|
.matrix = matrix,
|
||||||
|
.post_transform_translation = state().translation.translation().to_rounded<int>(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayListRecorder::apply_mask_bitmap(Gfx::IntPoint origin, Gfx::Bitmap const& bitmap, Gfx::Bitmap::MaskKind kind)
|
||||||
|
{
|
||||||
|
append(ApplyMaskBitmap {
|
||||||
|
.origin = origin,
|
||||||
|
.bitmap = bitmap,
|
||||||
|
.kind = kind,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,6 @@ public:
|
||||||
bool is_fixed_position;
|
bool is_fixed_position;
|
||||||
Gfx::IntRect source_paintable_rect;
|
Gfx::IntRect source_paintable_rect;
|
||||||
StackingContextTransform transform;
|
StackingContextTransform transform;
|
||||||
Optional<StackingContextMask> mask = {};
|
|
||||||
Optional<Gfx::Path> clip_path = {};
|
Optional<Gfx::Path> clip_path = {};
|
||||||
};
|
};
|
||||||
void push_stacking_context(PushStackingContextParams params);
|
void push_stacking_context(PushStackingContextParams params);
|
||||||
|
@ -147,6 +146,10 @@ public:
|
||||||
|
|
||||||
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical);
|
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical);
|
||||||
|
|
||||||
|
void apply_opacity(float opacity);
|
||||||
|
void apply_transform(Gfx::FloatPoint origin, Gfx::FloatMatrix4x4);
|
||||||
|
void apply_mask_bitmap(Gfx::IntPoint origin, Gfx::Bitmap const&, Gfx::Bitmap::MaskKind);
|
||||||
|
|
||||||
DisplayListRecorder(DisplayList&);
|
DisplayListRecorder(DisplayList&);
|
||||||
~DisplayListRecorder();
|
~DisplayListRecorder();
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ namespace Web::Painting {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element)
|
static T const* first_child_layout_node_of_type(SVG::SVGGraphicsElement const& graphics_element)
|
||||||
{
|
{
|
||||||
|
if (!graphics_element.layout_node())
|
||||||
|
return nullptr;
|
||||||
return graphics_element.layout_node()->first_child_of_type<T>();
|
return graphics_element.layout_node()->first_child_of_type<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ RefPtr<Gfx::Bitmap> SVGMaskable::calculate_mask_of_svg(PaintContext& context, CS
|
||||||
auto paint_context = context.clone(display_list_recorder);
|
auto paint_context = context.clone(display_list_recorder);
|
||||||
paint_context.set_svg_transform(graphics_element.get_transform());
|
paint_context.set_svg_transform(graphics_element.get_transform());
|
||||||
paint_context.set_draw_svg_geometry_for_clip_path(is<SVGClipPaintable>(paintable));
|
paint_context.set_draw_svg_geometry_for_clip_path(is<SVGClipPaintable>(paintable));
|
||||||
StackingContext::paint_node_as_stacking_context(paintable, paint_context);
|
StackingContext::paint_svg(paint_context, paintable, PaintPhase::Foreground);
|
||||||
DisplayListPlayerSkia display_list_player { *mask_bitmap };
|
DisplayListPlayerSkia display_list_player { *mask_bitmap };
|
||||||
display_list_player.execute(display_list);
|
display_list_player.execute(display_list);
|
||||||
return mask_bitmap;
|
return mask_bitmap;
|
||||||
|
|
|
@ -43,4 +43,69 @@ void SVGSVGPaintable::after_children_paint(PaintContext& context, PaintPhase pha
|
||||||
context.display_list_recorder().restore();
|
context.display_list_recorder().restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Gfx::FloatMatrix4x4 matrix_with_scaled_translation(Gfx::FloatMatrix4x4 matrix, float scale)
|
||||||
|
{
|
||||||
|
auto* m = matrix.elements();
|
||||||
|
m[0][3] *= scale;
|
||||||
|
m[1][3] *= scale;
|
||||||
|
m[2][3] *= scale;
|
||||||
|
return matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SVGSVGPaintable::paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase)
|
||||||
|
{
|
||||||
|
if (phase != PaintPhase::Foreground)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto paint_svg_box = [&](auto& svg_box) {
|
||||||
|
auto const& computed_values = svg_box.computed_values();
|
||||||
|
|
||||||
|
auto masking_area = svg_box.get_masking_area();
|
||||||
|
auto needs_to_save_state = computed_values.opacity() < 1 || svg_box.has_css_transform() || svg_box.get_masking_area().has_value();
|
||||||
|
|
||||||
|
if (needs_to_save_state) {
|
||||||
|
context.display_list_recorder().save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (computed_values.opacity() < 1) {
|
||||||
|
context.display_list_recorder().apply_opacity(computed_values.opacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svg_box.has_css_transform()) {
|
||||||
|
auto transform_matrix = svg_box.transform();
|
||||||
|
Gfx::FloatPoint transform_origin = svg_box.transform_origin().template to_type<float>();
|
||||||
|
auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
|
||||||
|
context.display_list_recorder().apply_transform(transform_origin.scaled(to_device_pixels_scale), matrix_with_scaled_translation(transform_matrix, to_device_pixels_scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (masking_area.has_value()) {
|
||||||
|
if (masking_area->is_empty())
|
||||||
|
return;
|
||||||
|
auto mask_bitmap = svg_box.calculate_mask(context, *masking_area);
|
||||||
|
if (mask_bitmap) {
|
||||||
|
auto source_paintable_rect = context.enclosing_device_rect(*masking_area).template to_type<int>();
|
||||||
|
auto origin = source_paintable_rect.location();
|
||||||
|
context.display_list_recorder().apply_mask_bitmap(origin, mask_bitmap.release_nonnull(), *svg_box.get_mask_type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg_box.before_paint(context, PaintPhase::Foreground);
|
||||||
|
svg_box.paint(context, PaintPhase::Foreground);
|
||||||
|
svg_box.after_paint(context, PaintPhase::Foreground);
|
||||||
|
|
||||||
|
paint_descendants(context, svg_box, phase);
|
||||||
|
|
||||||
|
if (needs_to_save_state) {
|
||||||
|
context.display_list_recorder().restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
paintable.before_children_paint(context, PaintPhase::Foreground);
|
||||||
|
paintable.for_each_child([&](auto& child) {
|
||||||
|
paint_svg_box(verify_cast<PaintableBox>(child));
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
});
|
||||||
|
paintable.after_children_paint(context, PaintPhase::Foreground);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ public:
|
||||||
|
|
||||||
Layout::SVGSVGBox const& layout_box() const;
|
Layout::SVGSVGBox const& layout_box() const;
|
||||||
|
|
||||||
|
static void paint_descendants(PaintContext& context, PaintableBox const& paintable, PaintPhase phase);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SVGSVGPaintable(Layout::SVGSVGBox const&);
|
SVGSVGPaintable(Layout::SVGSVGBox const&);
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,19 +5,16 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <AK/Debug.h>
|
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
#include <AK/StringBuilder.h>
|
|
||||||
#include <LibGfx/AffineTransform.h>
|
#include <LibGfx/AffineTransform.h>
|
||||||
#include <LibGfx/Matrix4x4.h>
|
#include <LibGfx/Matrix4x4.h>
|
||||||
#include <LibGfx/Rect.h>
|
#include <LibGfx/Rect.h>
|
||||||
#include <LibWeb/CSS/ComputedValues.h>
|
|
||||||
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
|
#include <LibWeb/CSS/StyleValues/TransformationStyleValue.h>
|
||||||
#include <LibWeb/Layout/Box.h>
|
#include <LibWeb/Layout/Box.h>
|
||||||
#include <LibWeb/Layout/ReplacedBox.h>
|
#include <LibWeb/Layout/ReplacedBox.h>
|
||||||
#include <LibWeb/Layout/Viewport.h>
|
#include <LibWeb/Layout/Viewport.h>
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
#include <LibWeb/Painting/PaintableBox.h>
|
||||||
#include <LibWeb/Painting/SVGPaintable.h>
|
#include <LibWeb/Painting/SVGSVGPaintable.h>
|
||||||
#include <LibWeb/Painting/StackingContext.h>
|
#include <LibWeb/Painting/StackingContext.h>
|
||||||
#include <LibWeb/SVG/SVGMaskElement.h>
|
#include <LibWeb/SVG/SVGMaskElement.h>
|
||||||
|
|
||||||
|
@ -81,6 +78,11 @@ static PaintPhase to_paint_phase(StackingContext::StackingContextPaintPhase phas
|
||||||
|
|
||||||
void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, PaintContext& context)
|
void StackingContext::paint_node_as_stacking_context(Paintable const& paintable, PaintContext& context)
|
||||||
{
|
{
|
||||||
|
if (paintable.layout_node().is_svg_svg_box()) {
|
||||||
|
paint_svg(context, static_cast<PaintableBox const&>(paintable), PaintPhase::Foreground);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VERIFY(!paintable.layout_node().is_svg_svg_box());
|
||||||
paint_node(paintable, context, PaintPhase::Background);
|
paint_node(paintable, context, PaintPhase::Background);
|
||||||
paint_node(paintable, context, PaintPhase::Border);
|
paint_node(paintable, context, PaintPhase::Border);
|
||||||
paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders);
|
paint_descendants(context, paintable, StackingContextPaintPhase::BackgroundAndBorders);
|
||||||
|
@ -93,6 +95,18 @@ void StackingContext::paint_node_as_stacking_context(Paintable const& paintable,
|
||||||
paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay);
|
paint_descendants(context, paintable, StackingContextPaintPhase::FocusAndOverlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StackingContext::paint_svg(PaintContext& context, PaintableBox const& paintable, PaintPhase phase)
|
||||||
|
{
|
||||||
|
if (phase != PaintPhase::Foreground)
|
||||||
|
return;
|
||||||
|
|
||||||
|
paintable.apply_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||||
|
paint_node(paintable, context, PaintPhase::Background);
|
||||||
|
paint_node(paintable, context, PaintPhase::Border);
|
||||||
|
SVGSVGPaintable::paint_descendants(context, paintable, phase);
|
||||||
|
paintable.clear_clip_overflow_rect(context, PaintPhase::Foreground);
|
||||||
|
}
|
||||||
|
|
||||||
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
|
void StackingContext::paint_descendants(PaintContext& context, Paintable const& paintable, StackingContextPaintPhase phase)
|
||||||
{
|
{
|
||||||
paintable.before_children_paint(context, to_paint_phase(phase));
|
paintable.before_children_paint(context, to_paint_phase(phase));
|
||||||
|
@ -101,6 +115,11 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
|
||||||
auto* stacking_context = child.stacking_context();
|
auto* stacking_context = child.stacking_context();
|
||||||
auto const& z_index = child.computed_values().z_index();
|
auto const& z_index = child.computed_values().z_index();
|
||||||
|
|
||||||
|
if (child.layout_node().is_svg_svg_box()) {
|
||||||
|
paint_svg(context, static_cast<PaintableBox const&>(child), to_paint_phase(phase));
|
||||||
|
return IterationDecision::Continue;
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
|
// NOTE: Grid specification https://www.w3.org/TR/css-grid-2/#z-order says that grid items should be treated
|
||||||
// the same way as CSS2 defines for inline-blocks:
|
// the same way as CSS2 defines for inline-blocks:
|
||||||
// "For each one of these, treat the element as if it created a new stacking context, but any positioned
|
// "For each one of these, treat the element as if it created a new stacking context, but any positioned
|
||||||
|
@ -174,6 +193,9 @@ void StackingContext::paint_descendants(PaintContext& context, Paintable const&
|
||||||
|
|
||||||
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
|
void StackingContext::paint_child(PaintContext& context, StackingContext const& child)
|
||||||
{
|
{
|
||||||
|
VERIFY(!child.paintable().layout_node().is_svg_box());
|
||||||
|
VERIFY(!child.paintable().layout_node().is_svg_svg_box());
|
||||||
|
|
||||||
const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id());
|
const_cast<StackingContext&>(child).set_last_paint_generation_id(context.paint_generation_id());
|
||||||
|
|
||||||
auto parent_paintable = child.paintable().parent();
|
auto parent_paintable = child.paintable().parent();
|
||||||
|
@ -188,6 +210,12 @@ void StackingContext::paint_child(PaintContext& context, StackingContext const&
|
||||||
|
|
||||||
void StackingContext::paint_internal(PaintContext& context) const
|
void StackingContext::paint_internal(PaintContext& context) const
|
||||||
{
|
{
|
||||||
|
VERIFY(!paintable().layout_node().is_svg_box());
|
||||||
|
if (paintable().layout_node().is_svg_svg_box()) {
|
||||||
|
paint_svg(context, paintable_box(), PaintPhase::Foreground);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// For a more elaborate description of the algorithm, see CSS 2.1 Appendix E
|
// For a more elaborate description of the algorithm, see CSS 2.1 Appendix E
|
||||||
// Draw the background and borders for the context root (steps 1, 2)
|
// Draw the background and borders for the context root (steps 1, 2)
|
||||||
paint_node(paintable(), context, PaintPhase::Background);
|
paint_node(paintable(), context, PaintPhase::Background);
|
||||||
|
@ -299,22 +327,6 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (paintable().is_paintable_box()) {
|
|
||||||
if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
|
|
||||||
if (masking_area->is_empty())
|
|
||||||
return;
|
|
||||||
auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
|
|
||||||
if (mask_bitmap) {
|
|
||||||
auto source_paintable_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
|
|
||||||
push_stacking_context_params.source_paintable_rect = source_paintable_rect;
|
|
||||||
push_stacking_context_params.mask = StackingContextMask {
|
|
||||||
.mask_bitmap = mask_bitmap.release_nonnull(),
|
|
||||||
.mask_kind = *paintable_box().get_mask_type()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto const& computed_values = paintable().computed_values();
|
auto const& computed_values = paintable().computed_values();
|
||||||
if (auto clip_path = computed_values.clip_path(); clip_path.has_value() && clip_path->is_basic_shape()) {
|
if (auto clip_path = computed_values.clip_path(); clip_path.has_value() && clip_path->is_basic_shape()) {
|
||||||
auto const& masking_area = paintable_box().get_masking_area();
|
auto const& masking_area = paintable_box().get_masking_area();
|
||||||
|
@ -332,6 +344,19 @@ void StackingContext::paint(PaintContext& context) const
|
||||||
if (paintable().is_paintable_box() && paintable_box().scroll_frame_id().has_value())
|
if (paintable().is_paintable_box() && paintable_box().scroll_frame_id().has_value())
|
||||||
context.display_list_recorder().set_scroll_frame_id(*paintable_box().scroll_frame_id());
|
context.display_list_recorder().set_scroll_frame_id(*paintable_box().scroll_frame_id());
|
||||||
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
||||||
|
|
||||||
|
if (paintable().is_paintable_box()) {
|
||||||
|
if (auto masking_area = paintable_box().get_masking_area(); masking_area.has_value()) {
|
||||||
|
if (masking_area->is_empty())
|
||||||
|
return;
|
||||||
|
auto mask_bitmap = paintable_box().calculate_mask(context, *masking_area);
|
||||||
|
if (mask_bitmap) {
|
||||||
|
auto masking_area_rect = context.enclosing_device_rect(*masking_area).to_type<int>();
|
||||||
|
context.display_list_recorder().apply_mask_bitmap(masking_area_rect.location(), mask_bitmap.release_nonnull(), *paintable_box().get_mask_type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
paint_internal(context);
|
paint_internal(context);
|
||||||
context.display_list_recorder().pop_stacking_context();
|
context.display_list_recorder().pop_stacking_context();
|
||||||
if (has_css_transform)
|
if (has_css_transform)
|
||||||
|
|
|
@ -36,6 +36,7 @@ public:
|
||||||
|
|
||||||
static void paint_node_as_stacking_context(Paintable const&, PaintContext&);
|
static void paint_node_as_stacking_context(Paintable const&, PaintContext&);
|
||||||
static void paint_descendants(PaintContext&, Paintable const&, StackingContextPaintPhase);
|
static void paint_descendants(PaintContext&, Paintable const&, StackingContextPaintPhase);
|
||||||
|
static void paint_svg(PaintContext&, PaintableBox const&, PaintPhase);
|
||||||
void paint(PaintContext&) const;
|
void paint(PaintContext&) const;
|
||||||
|
|
||||||
[[nodiscard]] TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
[[nodiscard]] TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue