mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
LibWeb: Use device-pixel coordinates in display list and AVC
Stop converting between CSS and device pixels as part of rendering - the display list should be as simple as possible, so convert to DevicePixels once when constructing the display list.
This commit is contained in:
parent
8d099b9cec
commit
90a211bf47
Notes:
github-actions[bot]
2026-02-26 06:44:15 +00:00
Author: https://github.com/gmta
Commit: 90a211bf47
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8162
Reviewed-by: https://github.com/kalenikaliaksandr ✅
23 changed files with 255 additions and 211 deletions
|
|
@ -100,7 +100,7 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
|
|||
painter->clear_rect(bitmap.rect().to_type<float>(), Color::Transparent);
|
||||
|
||||
// Paint the cursor into a bitmap.
|
||||
auto display_list = Painting::DisplayList::create(document.page().client().device_pixels_per_css_pixel());
|
||||
auto display_list = Painting::DisplayList::create();
|
||||
Painting::DisplayListRecorder display_list_recorder(display_list);
|
||||
DisplayListRecordingContext paint_context { display_list_recorder, document.page().palette(), document.page().client().device_pixels_per_css_pixel(), document.page().chrome_metrics() };
|
||||
|
||||
|
|
|
|||
|
|
@ -7279,7 +7279,7 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
|
|||
if (m_cached_display_list && m_cached_display_list_paint_config == config)
|
||||
return m_cached_display_list;
|
||||
|
||||
auto display_list = Painting::DisplayList::create(page().client().device_pixels_per_css_pixel());
|
||||
auto display_list = Painting::DisplayList::create();
|
||||
Painting::DisplayListRecorder display_list_recorder(display_list);
|
||||
|
||||
// https://drafts.csswg.org/css-color-adjust-1/#color-scheme-effect
|
||||
|
|
|
|||
|
|
@ -1434,10 +1434,10 @@ Vector<CSSPixelRect> Element::get_client_rects() const
|
|||
auto absolute_rect = paintable_box->absolute_border_box_rect();
|
||||
|
||||
if (auto const& accumulated_visual_context = paintable_box->accumulated_visual_context()) {
|
||||
auto const& viewport_paintable = *document().paintable();
|
||||
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
|
||||
auto transformed_rect = accumulated_visual_context->transform_rect_to_viewport(absolute_rect, scroll_state);
|
||||
rects.append(transformed_rect);
|
||||
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
|
||||
auto result = accumulated_visual_context->transform_rect_to_viewport(absolute_rect.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
rects.append((result * (1.f / pixel_ratio)).to_type<CSSPixels>());
|
||||
} else {
|
||||
rects.append(absolute_rect);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <LibWeb/HTML/Navigable.h>
|
||||
#include <LibWeb/Page/AutoScrollHandler.h>
|
||||
#include <LibWeb/Page/EventHandler.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
||||
|
|
@ -49,8 +50,10 @@ static Optional<CSSPixelRect> scrollport_rect_in_viewport(Painting::PaintableBox
|
|||
auto const* viewport_paintable = paintable_box.document().paintable();
|
||||
if (!viewport_paintable)
|
||||
return {};
|
||||
auto pixel_ratio = static_cast<float>(paintable_box.document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = viewport_paintable->scroll_state_snapshot();
|
||||
return accumulated_visual_context->transform_rect_to_viewport(scrollport, scroll_state);
|
||||
auto result = accumulated_visual_context->transform_rect_to_viewport(scrollport.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
return (result * (1.f / pixel_ratio)).to_type<CSSPixels>();
|
||||
}
|
||||
|
||||
// Returns scroll speed in CSS pixels per second for each axis, based on how far the mouse is past the auto scroll edge.
|
||||
|
|
|
|||
|
|
@ -206,8 +206,9 @@ static CSSPixelPoint compute_mouse_event_offset(CSSPixelPoint position, Painting
|
|||
visual_context = containing_block->accumulated_visual_context();
|
||||
}
|
||||
if (visual_context) {
|
||||
auto transformed = visual_context->inverse_transform_point(position);
|
||||
precision_offset = { transformed.x().to_double(), transformed.y().to_double() };
|
||||
auto pixel_ratio = static_cast<float>(paintable.document().page().client().device_pixels_per_css_pixel());
|
||||
auto result = visual_context->inverse_transform_point(position.to_type<float>() * pixel_ratio);
|
||||
precision_offset = result / pixel_ratio;
|
||||
}
|
||||
|
||||
// relative to the origin of the padding edge of the target node
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ NonnullRefPtr<AccumulatedVisualContext> AccumulatedVisualContext::create(size_t
|
|||
return adopt_ref(*new AccumulatedVisualContext(id, move(data), move(parent)));
|
||||
}
|
||||
|
||||
bool ClipData::contains(CSSPixelPoint point) const
|
||||
bool ClipData::contains(DevicePixelPoint point) const
|
||||
{
|
||||
return corner_radii.contains(point, rect);
|
||||
return corner_radii.contains(point.to_type<int>(), rect.to_type<int>());
|
||||
}
|
||||
|
||||
Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(CSSPixelPoint screen_point, ScrollStateSnapshot const& scroll_state) const
|
||||
Optional<Gfx::FloatPoint> AccumulatedVisualContext::transform_point_for_hit_test(Gfx::FloatPoint screen_point, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const
|
||||
{
|
||||
Vector<AccumulatedVisualContext const*, 8> chain;
|
||||
chain.ensure_capacity(m_depth);
|
||||
|
|
@ -33,47 +33,47 @@ Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(C
|
|||
auto const* node = chain[i - 1];
|
||||
|
||||
auto result = node->data().visit(
|
||||
[&](PerspectiveData const& perspective) -> Optional<CSSPixelPoint> {
|
||||
[&](PerspectiveData const& perspective) -> Optional<Gfx::FloatPoint> {
|
||||
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
if (!inverse.has_value())
|
||||
return {};
|
||||
point = inverse->map(point.to_type<float>()).to_type<CSSPixels>();
|
||||
point = inverse->map(point);
|
||||
return point;
|
||||
},
|
||||
[&](ScrollData const& scroll) -> Optional<CSSPixelPoint> {
|
||||
auto offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
|
||||
point.translate_by(-offset);
|
||||
[&](ScrollData const& scroll) -> Optional<Gfx::FloatPoint> {
|
||||
if (scroll.scroll_frame_id < scroll_offsets.size())
|
||||
point.translate_by(-scroll_offsets[scroll.scroll_frame_id]);
|
||||
return point;
|
||||
},
|
||||
[&](TransformData const& transform) -> Optional<CSSPixelPoint> {
|
||||
[&](TransformData const& transform) -> Optional<Gfx::FloatPoint> {
|
||||
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
if (!inverse.has_value())
|
||||
return {};
|
||||
|
||||
auto offset_point = point - transform.origin;
|
||||
auto transformed = inverse->map(offset_point.to_type<float>()).to_type<CSSPixels>();
|
||||
auto transformed = inverse->map(offset_point);
|
||||
point = transformed + transform.origin;
|
||||
return point;
|
||||
},
|
||||
[&](ClipData const& clip) -> Optional<CSSPixelPoint> {
|
||||
// NOTE: The clip rect is stored in absolute (layout) coordinates. After inverse-transforming, `point`
|
||||
// is also in layout coordinates, so we compare them directly without mapping back to screen space.
|
||||
if (!clip.contains(point))
|
||||
[&](ClipData const& clip) -> Optional<Gfx::FloatPoint> {
|
||||
// NOTE: The clip rect is in absolute device-pixel coordinates. After inverse-transforming, `point`
|
||||
// is also in device-pixel coordinates, so we compare them directly.
|
||||
if (!clip.contains(point.to_type<int>().to_type<DevicePixels>()))
|
||||
return {};
|
||||
return point;
|
||||
},
|
||||
[&](ClipPathData const& clip_path) -> Optional<CSSPixelPoint> {
|
||||
// NOTE: The clip path is stored in absolute (layout) coordinates. After inverse-transforming, `point`
|
||||
// is also in layout coordinates, so we compare them directly without mapping back to screen space.
|
||||
if (!clip_path.bounding_rect.contains(point))
|
||||
[&](ClipPathData const& clip_path) -> Optional<Gfx::FloatPoint> {
|
||||
// NOTE: The clip path is in absolute device-pixel coordinates. After inverse-transforming, `point`
|
||||
// is also in device-pixel coordinates, so we compare them directly.
|
||||
if (!clip_path.bounding_rect.contains(point.to_type<int>().to_type<DevicePixels>()))
|
||||
return {};
|
||||
if (!clip_path.path.contains(point.to_type<float>(), clip_path.fill_rule))
|
||||
if (!clip_path.path.contains(point, clip_path.fill_rule))
|
||||
return {};
|
||||
return point;
|
||||
},
|
||||
[&](EffectsData const&) -> Optional<CSSPixelPoint> {
|
||||
[&](EffectsData const&) -> Optional<Gfx::FloatPoint> {
|
||||
// Effects don't affect coordinate transforms
|
||||
return point;
|
||||
});
|
||||
|
|
@ -85,7 +85,7 @@ Optional<CSSPixelPoint> AccumulatedVisualContext::transform_point_for_hit_test(C
|
|||
return point;
|
||||
}
|
||||
|
||||
CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint screen_point) const
|
||||
Gfx::FloatPoint AccumulatedVisualContext::inverse_transform_point(Gfx::FloatPoint screen_point) const
|
||||
{
|
||||
Vector<AccumulatedVisualContext const*, 8> chain;
|
||||
chain.ensure_capacity(m_depth);
|
||||
|
|
@ -101,14 +101,14 @@ CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint sc
|
|||
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
if (inverse.has_value())
|
||||
point = inverse->map(point.to_type<float>()).to_type<CSSPixels>();
|
||||
point = inverse->map(point);
|
||||
},
|
||||
[&](TransformData const& transform) {
|
||||
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
|
||||
auto inverse = affine.inverse();
|
||||
if (inverse.has_value()) {
|
||||
auto offset_point = point - transform.origin;
|
||||
auto transformed = inverse->map(offset_point.to_type<float>()).to_type<CSSPixels>();
|
||||
auto transformed = inverse->map(offset_point);
|
||||
point = transformed + transform.origin;
|
||||
}
|
||||
},
|
||||
|
|
@ -118,32 +118,31 @@ CSSPixelPoint AccumulatedVisualContext::inverse_transform_point(CSSPixelPoint sc
|
|||
return point;
|
||||
}
|
||||
|
||||
CSSPixelRect AccumulatedVisualContext::transform_rect_to_viewport(CSSPixelRect const& source_rect, ScrollStateSnapshot const& scroll_state) const
|
||||
Gfx::FloatRect AccumulatedVisualContext::transform_rect_to_viewport(Gfx::FloatRect const& source_rect, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const
|
||||
{
|
||||
auto rect = source_rect.to_type<float>();
|
||||
auto rect = source_rect;
|
||||
for (auto const* node = this; node; node = node->parent().ptr()) {
|
||||
node->data().visit(
|
||||
[&](TransformData const& transform) {
|
||||
auto affine = Gfx::extract_2d_affine_transform(transform.matrix);
|
||||
auto origin = transform.origin.to_type<float>();
|
||||
rect.translate_by(-origin);
|
||||
rect.translate_by(-transform.origin);
|
||||
rect = affine.map(rect);
|
||||
rect.translate_by(origin);
|
||||
rect.translate_by(transform.origin);
|
||||
},
|
||||
[&](PerspectiveData const& perspective) {
|
||||
auto affine = Gfx::extract_2d_affine_transform(perspective.matrix);
|
||||
rect = affine.map(rect);
|
||||
},
|
||||
[&](ScrollData const& scroll) {
|
||||
auto offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
|
||||
rect.translate_by(offset.to_type<float>());
|
||||
if (scroll.scroll_frame_id < scroll_offsets.size())
|
||||
rect.translate_by(scroll_offsets[scroll.scroll_frame_id]);
|
||||
},
|
||||
[&](ClipData const&) { /* clips don't affect rect coordinates */ },
|
||||
[&](ClipPathData const&) { /* clip paths don't affect rect coordinates */ },
|
||||
[&](EffectsData const&) { /* effects don't affect rect coordinates */ });
|
||||
}
|
||||
|
||||
return rect.to_type<CSSPixels>();
|
||||
return rect;
|
||||
}
|
||||
|
||||
void AccumulatedVisualContext::dump(StringBuilder& builder) const
|
||||
|
|
@ -160,11 +159,11 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
|
|||
[&](TransformData const& transform) {
|
||||
auto const& matrix = transform.matrix.elements();
|
||||
auto const& origin = transform.origin;
|
||||
builder.appendff("transform=[{},{},{},{},{},{}] origin=({},{})", matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[0][3], matrix[1][3], origin.x().to_float(), origin.y().to_float());
|
||||
builder.appendff("transform=[{},{},{},{},{},{}] origin=({},{})", matrix[0][0], matrix[0][1], matrix[1][0], matrix[1][1], matrix[0][3], matrix[1][3], origin.x(), origin.y());
|
||||
},
|
||||
[&](ClipData const& clip) {
|
||||
auto const& rect = clip.rect;
|
||||
builder.appendff("clip=[{},{} {}x{}]", rect.x().to_float(), rect.y().to_float(), rect.width().to_float(), rect.height().to_float());
|
||||
builder.appendff("clip=[{},{} {}x{}]", rect.x(), rect.y(), rect.width(), rect.height());
|
||||
|
||||
if (clip.corner_radii.has_any_radius()) {
|
||||
auto const& corner_radii = clip.corner_radii;
|
||||
|
|
@ -173,7 +172,7 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
|
|||
},
|
||||
[&](ClipPathData const& clip_path) {
|
||||
auto const& rect = clip_path.bounding_rect;
|
||||
builder.appendff("clip_path=[bounds: {},{} {}x{}, path: {}]", rect.x().to_float(), rect.y().to_float(), rect.width().to_float(), rect.height().to_float(), clip_path.path.to_svg_string());
|
||||
builder.appendff("clip_path=[bounds: {},{} {}x{}, path: {}]", rect.x(), rect.y(), rect.width(), rect.height(), clip_path.path.to_svg_string());
|
||||
},
|
||||
[&](EffectsData const& effects) {
|
||||
builder.append("effects=["sv);
|
||||
|
|
@ -188,10 +187,10 @@ void AccumulatedVisualContext::dump(StringBuilder& builder) const
|
|||
builder.appendff("blend_mode={}", static_cast<int>(effects.blend_mode));
|
||||
has_content = true;
|
||||
}
|
||||
if (effects.filter.has_filters()) {
|
||||
if (effects.gfx_filter.has_value()) {
|
||||
if (has_content)
|
||||
builder.append(' ');
|
||||
effects.filter.dump(builder);
|
||||
builder.append("filter"sv);
|
||||
has_content = true;
|
||||
}
|
||||
builder.append("]"sv);
|
||||
|
|
|
|||
|
|
@ -9,47 +9,38 @@
|
|||
#include <AK/AtomicRefCounted.h>
|
||||
#include <AK/Variant.h>
|
||||
#include <LibGfx/CompositingAndBlendingOperator.h>
|
||||
#include <LibGfx/Filter.h>
|
||||
#include <LibGfx/Matrix4x4.h>
|
||||
#include <LibGfx/Path.h>
|
||||
#include <LibGfx/Point.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibGfx/WindingRule.h>
|
||||
#include <LibWeb/Painting/BorderRadiiData.h>
|
||||
#include <LibWeb/Painting/ResolvedCSSFilter.h>
|
||||
#include <LibWeb/Painting/ScrollState.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
struct ClipRect {
|
||||
CSSPixelRect rect;
|
||||
BorderRadiiData corner_radii;
|
||||
};
|
||||
|
||||
struct ScrollData {
|
||||
size_t scroll_frame_id;
|
||||
bool is_sticky;
|
||||
};
|
||||
|
||||
struct ClipData {
|
||||
CSSPixelRect rect;
|
||||
BorderRadiiData corner_radii;
|
||||
DevicePixelRect rect;
|
||||
CornerRadii corner_radii;
|
||||
|
||||
explicit ClipData(ClipRect const& clip_rect)
|
||||
: rect(clip_rect.rect)
|
||||
, corner_radii(clip_rect.corner_radii)
|
||||
{
|
||||
}
|
||||
|
||||
ClipData(CSSPixelRect r, BorderRadiiData radii)
|
||||
ClipData(DevicePixelRect r, CornerRadii radii)
|
||||
: rect(r)
|
||||
, corner_radii(radii)
|
||||
{
|
||||
}
|
||||
|
||||
bool contains(CSSPixelPoint point) const;
|
||||
bool contains(DevicePixelPoint point) const;
|
||||
};
|
||||
|
||||
struct TransformData {
|
||||
Gfx::FloatMatrix4x4 matrix;
|
||||
CSSPixelPoint origin;
|
||||
Gfx::FloatPoint origin;
|
||||
};
|
||||
|
||||
struct PerspectiveData {
|
||||
|
|
@ -58,20 +49,20 @@ struct PerspectiveData {
|
|||
|
||||
struct ClipPathData {
|
||||
Gfx::Path path;
|
||||
CSSPixelRect bounding_rect;
|
||||
DevicePixelRect bounding_rect;
|
||||
Gfx::WindingRule fill_rule;
|
||||
};
|
||||
|
||||
struct EffectsData {
|
||||
float opacity { 1.0f };
|
||||
Gfx::CompositingAndBlendingOperator blend_mode { Gfx::CompositingAndBlendingOperator::Normal };
|
||||
ResolvedCSSFilter filter;
|
||||
Optional<Gfx::Filter> gfx_filter;
|
||||
|
||||
bool needs_layer() const
|
||||
{
|
||||
return opacity < 1.0f
|
||||
|| blend_mode != Gfx::CompositingAndBlendingOperator::Normal
|
||||
|| filter.has_filters();
|
||||
|| gfx_filter.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -92,9 +83,9 @@ public:
|
|||
|
||||
void dump(StringBuilder&) const;
|
||||
|
||||
Optional<CSSPixelPoint> transform_point_for_hit_test(CSSPixelPoint screen_point, ScrollStateSnapshot const& scroll_state) const;
|
||||
CSSPixelPoint inverse_transform_point(CSSPixelPoint point) const;
|
||||
CSSPixelRect transform_rect_to_viewport(CSSPixelRect const&, ScrollStateSnapshot const&) const;
|
||||
Optional<Gfx::FloatPoint> transform_point_for_hit_test(Gfx::FloatPoint, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const;
|
||||
Gfx::FloatPoint inverse_transform_point(Gfx::FloatPoint) const;
|
||||
Gfx::FloatRect transform_rect_to_viewport(Gfx::FloatRect const&, ReadonlySpan<Gfx::FloatPoint> scroll_offsets) const;
|
||||
|
||||
private:
|
||||
AccumulatedVisualContext(size_t id, VisualContextData data, RefPtr<AccumulatedVisualContext const> parent)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Web::Painting {
|
|||
|
||||
static RefPtr<DisplayList> compute_text_clip_paths(DisplayListRecordingContext& context, Paintable const& paintable, CSSPixelPoint containing_block_location)
|
||||
{
|
||||
auto text_clip_paths = DisplayList::create(context.device_pixels_per_css_pixel());
|
||||
auto text_clip_paths = DisplayList::create();
|
||||
DisplayListRecorder display_list_recorder(*text_clip_paths);
|
||||
// Remove containing block offset, so executing the display list will produce mask at (0, 0)
|
||||
display_list_recorder.translate(-context.floored_device_point(containing_block_location).to_type<int>());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Point.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/Export.h>
|
||||
|
||||
|
|
@ -52,20 +54,8 @@ struct CornerRadii {
|
|||
{
|
||||
return top_left || top_right || bottom_right || bottom_left;
|
||||
}
|
||||
};
|
||||
|
||||
struct BorderRadiiData {
|
||||
BorderRadiusData top_left;
|
||||
BorderRadiusData top_right;
|
||||
BorderRadiusData bottom_right;
|
||||
BorderRadiusData bottom_left;
|
||||
|
||||
inline bool has_any_radius() const
|
||||
{
|
||||
return top_left || top_right || bottom_right || bottom_left;
|
||||
}
|
||||
|
||||
bool contains(CSSPixelPoint point, CSSPixelRect const& rect) const
|
||||
bool contains(Gfx::IntPoint point, Gfx::IntRect const& rect) const
|
||||
{
|
||||
if (!rect.contains(point))
|
||||
return false;
|
||||
|
|
@ -76,10 +66,10 @@ struct BorderRadiiData {
|
|||
auto const px = point.x();
|
||||
auto const py = point.y();
|
||||
|
||||
auto outside_ellipse = [&](BorderRadiusData const& r, CSSPixels cx, CSSPixels cy) {
|
||||
auto dx = (px - cx).to_float() / r.horizontal_radius.to_float();
|
||||
auto dy = (py - cy).to_float() / r.vertical_radius.to_float();
|
||||
return dx * dx + dy * dy > 1.0f;
|
||||
auto outside_ellipse = [&](CornerRadius const& r, int cx, int cy) {
|
||||
auto dx = static_cast<float>(px - cx) / r.horizontal_radius;
|
||||
auto dy = static_cast<float>(py - cy) / r.vertical_radius;
|
||||
return dx * dx + dy * dy > 1.f;
|
||||
};
|
||||
|
||||
if (top_left) {
|
||||
|
|
@ -112,6 +102,33 @@ struct BorderRadiiData {
|
|||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct BorderRadiiData {
|
||||
BorderRadiusData top_left;
|
||||
BorderRadiusData top_right;
|
||||
BorderRadiusData bottom_right;
|
||||
BorderRadiusData bottom_left;
|
||||
|
||||
inline bool has_any_radius() const
|
||||
{
|
||||
return top_left || top_right || bottom_right || bottom_left;
|
||||
}
|
||||
|
||||
bool contains(CSSPixelPoint point, CSSPixelRect const& rect) const
|
||||
{
|
||||
if (!rect.contains(point))
|
||||
return false;
|
||||
|
||||
if (!has_any_radius())
|
||||
return true;
|
||||
|
||||
auto to_corner = [](BorderRadiusData const& r) -> CornerRadius {
|
||||
return { static_cast<int>(r.horizontal_radius.to_float()), static_cast<int>(r.vertical_radius.to_float()) };
|
||||
};
|
||||
CornerRadii corners { to_corner(top_left), to_corner(top_right), to_corner(bottom_right), to_corner(bottom_left) };
|
||||
return corners.contains(point.to_type<int>(), rect.to_type<int>());
|
||||
}
|
||||
|
||||
inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibGfx/PaintingSurface.h>
|
||||
#include <LibWeb/Painting/DevicePixelConverter.h>
|
||||
#include <LibWeb/Painting/DisplayList.h>
|
||||
#include <LibWeb/Painting/ResolvedCSSFilter.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
|
|
@ -75,27 +73,9 @@ static RefPtr<AccumulatedVisualContext const> find_common_ancestor(RefPtr<Accumu
|
|||
return a;
|
||||
}
|
||||
|
||||
// Converts a CSS-pixel-space 4x4 matrix to device-pixel-space.
|
||||
// - Translation column (column 3, rows 0-2) is scaled up by DPR
|
||||
// - Perspective row (row 3, columns 0-2) is scaled down by DPR
|
||||
// - All other elements are unaffected (the scale factors cancel out)
|
||||
static FloatMatrix4x4 scale_matrix_for_device_pixels(FloatMatrix4x4 matrix, float scale)
|
||||
{
|
||||
matrix[0, 3] *= scale;
|
||||
matrix[1, 3] *= scale;
|
||||
matrix[2, 3] *= scale;
|
||||
matrix[3, 0] /= scale;
|
||||
matrix[3, 1] /= scale;
|
||||
matrix[3, 2] /= scale;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnapshot const& scroll_state)
|
||||
{
|
||||
auto const& commands = display_list.commands();
|
||||
auto device_pixels_per_css_pixel = display_list.device_pixels_per_css_pixel();
|
||||
|
||||
DevicePixelConverter device_pixel_converter { device_pixels_per_css_pixel };
|
||||
|
||||
VERIFY(m_surface);
|
||||
|
||||
|
|
@ -109,43 +89,35 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
|||
auto apply_accumulated_visual_context = [&](AccumulatedVisualContext const& node) {
|
||||
node.data().visit(
|
||||
[&](EffectsData const& effects) {
|
||||
Optional<Gfx::Filter> gfx_filter;
|
||||
if (effects.filter.has_filters())
|
||||
gfx_filter = to_gfx_filter(effects.filter, device_pixels_per_css_pixel);
|
||||
apply_effects({ .opacity = effects.opacity, .compositing_and_blending_operator = effects.blend_mode, .filter = gfx_filter });
|
||||
apply_effects({ .opacity = effects.opacity, .compositing_and_blending_operator = effects.blend_mode, .filter = effects.gfx_filter });
|
||||
},
|
||||
[&](PerspectiveData const& perspective) {
|
||||
save({});
|
||||
auto matrix = scale_matrix_for_device_pixels(perspective.matrix, static_cast<float>(device_pixels_per_css_pixel));
|
||||
apply_transform({ 0, 0 }, matrix);
|
||||
apply_transform({ 0, 0 }, perspective.matrix);
|
||||
},
|
||||
[&](ScrollData const& scroll) {
|
||||
save({});
|
||||
auto own_offset = scroll_state.own_offset_for_frame_with_id(scroll.scroll_frame_id);
|
||||
if (!own_offset.is_zero()) {
|
||||
auto scroll_offset = own_offset.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<int>();
|
||||
translate({ .delta = scroll_offset });
|
||||
auto const& offsets = scroll_state.device_offsets();
|
||||
if (scroll.scroll_frame_id < offsets.size()) {
|
||||
auto const& offset = offsets[scroll.scroll_frame_id];
|
||||
if (!offset.is_zero())
|
||||
translate({ .delta = offset.to_type<int>() });
|
||||
}
|
||||
},
|
||||
[&](TransformData const& transform) {
|
||||
save({});
|
||||
auto origin = transform.origin.to_type<double>().scaled(device_pixels_per_css_pixel).to_type<float>();
|
||||
auto matrix = scale_matrix_for_device_pixels(transform.matrix, static_cast<float>(device_pixels_per_css_pixel));
|
||||
apply_transform(origin, matrix);
|
||||
apply_transform(transform.origin, transform.matrix);
|
||||
},
|
||||
[&](ClipData const& clip) {
|
||||
save({});
|
||||
auto device_rect = device_pixel_converter.rounded_device_rect(clip.rect).to_type<int>();
|
||||
auto corner_radii = clip.corner_radii.as_corners(device_pixel_converter);
|
||||
if (corner_radii.has_any_radius())
|
||||
add_rounded_rect_clip({ .corner_radii = corner_radii, .border_rect = device_rect, .corner_clip = CornerClip::Outside });
|
||||
if (clip.corner_radii.has_any_radius())
|
||||
add_rounded_rect_clip({ .corner_radii = clip.corner_radii, .border_rect = clip.rect.to_type<int>(), .corner_clip = CornerClip::Outside });
|
||||
else
|
||||
add_clip_rect({ .rect = device_rect });
|
||||
add_clip_rect({ .rect = clip.rect.to_type<int>() });
|
||||
},
|
||||
[&](ClipPathData const& clip_path) {
|
||||
save({});
|
||||
auto transformed_path = clip_path.path.copy_transformed(Gfx::AffineTransform {}.set_scale(static_cast<float>(device_pixels_per_css_pixel), static_cast<float>(device_pixels_per_css_pixel)));
|
||||
add_clip_path(transformed_path);
|
||||
add_clip_path(clip_path.path);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -195,13 +167,13 @@ void DisplayListPlayer::execute_impl(DisplayList& display_list, ScrollStateSnaps
|
|||
if (command.has<PaintScrollBar>()) {
|
||||
auto translated_command = command;
|
||||
auto& paint_scroll_bar = translated_command.get<PaintScrollBar>();
|
||||
auto scroll_offset = scroll_state.own_offset_for_frame_with_id(paint_scroll_bar.scroll_frame_id);
|
||||
if (paint_scroll_bar.vertical) {
|
||||
auto offset = scroll_offset.y() * paint_scroll_bar.scroll_size;
|
||||
paint_scroll_bar.thumb_rect.translate_by(0, -offset.to_int() * device_pixels_per_css_pixel);
|
||||
} else {
|
||||
auto offset = scroll_offset.x() * paint_scroll_bar.scroll_size;
|
||||
paint_scroll_bar.thumb_rect.translate_by(-offset.to_int() * device_pixels_per_css_pixel, 0);
|
||||
auto const& offsets = scroll_state.device_offsets();
|
||||
if (paint_scroll_bar.scroll_frame_id < static_cast<int>(offsets.size())) {
|
||||
auto const& device_offset = offsets[paint_scroll_bar.scroll_frame_id];
|
||||
if (paint_scroll_bar.vertical)
|
||||
paint_scroll_bar.thumb_rect.translate_by(0, static_cast<int>(-device_offset.y() * paint_scroll_bar.scroll_size));
|
||||
else
|
||||
paint_scroll_bar.thumb_rect.translate_by(static_cast<int>(-device_offset.x() * paint_scroll_bar.scroll_size), 0);
|
||||
}
|
||||
paint_scrollbar(paint_scroll_bar);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ private:
|
|||
|
||||
class DisplayList : public AtomicRefCounted<DisplayList> {
|
||||
public:
|
||||
static NonnullRefPtr<DisplayList> create(double device_pixels_per_css_pixel)
|
||||
static NonnullRefPtr<DisplayList> create()
|
||||
{
|
||||
return adopt_ref(*new DisplayList(device_pixels_per_css_pixel));
|
||||
return adopt_ref(*new DisplayList());
|
||||
}
|
||||
|
||||
void append(DisplayListCommand&& command, RefPtr<AccumulatedVisualContext const> context);
|
||||
|
|
@ -86,16 +86,11 @@ public:
|
|||
|
||||
auto& commands(Badge<DisplayListRecorder>) { return m_commands; }
|
||||
auto const& commands() const { return m_commands; }
|
||||
double device_pixels_per_css_pixel() const { return m_device_pixels_per_css_pixel; }
|
||||
|
||||
private:
|
||||
DisplayList(double device_pixels_per_css_pixel)
|
||||
: m_device_pixels_per_css_pixel(device_pixels_per_css_pixel)
|
||||
{
|
||||
}
|
||||
DisplayList() = default;
|
||||
|
||||
AK::SegmentedVector<CommandListItem, 512> m_commands;
|
||||
double m_device_pixels_per_css_pixel;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ struct ApplyBackdropFilter {
|
|||
static constexpr StringView command_name = "ApplyBackdropFilter"sv;
|
||||
|
||||
Gfx::IntRect backdrop_region;
|
||||
BorderRadiiData border_radii_data;
|
||||
CornerRadii corner_radii;
|
||||
Optional<Gfx::Filter> backdrop_filter;
|
||||
|
||||
[[nodiscard]] Gfx::IntRect bounding_rect() const { return backdrop_region; }
|
||||
|
|
@ -344,7 +344,7 @@ struct PaintScrollBar {
|
|||
int scroll_frame_id { 0 };
|
||||
Gfx::IntRect gutter_rect;
|
||||
Gfx::IntRect thumb_rect;
|
||||
CSSPixelFraction scroll_size;
|
||||
double scroll_size;
|
||||
Color thumb_color;
|
||||
Color track_color;
|
||||
bool vertical;
|
||||
|
|
|
|||
|
|
@ -285,13 +285,13 @@ void DisplayListRecorder::restore()
|
|||
APPEND(Restore {});
|
||||
}
|
||||
|
||||
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter)
|
||||
void DisplayListRecorder::apply_backdrop_filter(Gfx::IntRect const& backdrop_region, CornerRadii const& corner_radii, Gfx::Filter const& backdrop_filter)
|
||||
{
|
||||
if (backdrop_region.is_empty())
|
||||
return;
|
||||
APPEND(ApplyBackdropFilter {
|
||||
.backdrop_region = backdrop_region,
|
||||
.border_radii_data = border_radii_data,
|
||||
.corner_radii = corner_radii,
|
||||
.backdrop_filter = backdrop_filter,
|
||||
});
|
||||
}
|
||||
|
|
@ -349,7 +349,7 @@ void DisplayListRecorder::fill_rect_with_rounded_corners(Gfx::IntRect const& a_r
|
|||
{ bottom_left_radius, bottom_left_radius } });
|
||||
}
|
||||
|
||||
void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, CSSPixelFraction scroll_size, Color thumb_color, Color track_color, bool vertical)
|
||||
void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, double scroll_size, Color thumb_color, Color track_color, bool vertical)
|
||||
{
|
||||
APPEND(PaintScrollBar {
|
||||
.scroll_frame_id = scroll_frame_id,
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ public:
|
|||
void begin_masks(ReadonlySpan<MaskInfo>);
|
||||
void end_masks(ReadonlySpan<MaskInfo>);
|
||||
|
||||
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, BorderRadiiData const& border_radii_data, Gfx::Filter const& backdrop_filter);
|
||||
void apply_backdrop_filter(Gfx::IntRect const& backdrop_region, CornerRadii const& corner_radii, Gfx::Filter const& backdrop_filter);
|
||||
|
||||
void paint_outer_box_shadow(PaintBoxShadowParams params);
|
||||
void paint_inner_box_shadow(PaintBoxShadowParams params);
|
||||
|
|
@ -116,7 +116,7 @@ public:
|
|||
void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int radius);
|
||||
void fill_rect_with_rounded_corners(Gfx::IntRect const& a_rect, Color color, int top_left_radius, int top_right_radius, int bottom_right_radius, int bottom_left_radius);
|
||||
|
||||
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, CSSPixelFraction scroll_size, Color thumb_color, Color track_color, bool vertical);
|
||||
void paint_scrollbar(int scroll_frame_id, Gfx::IntRect gutter_rect, Gfx::IntRect thumb_rect, double scroll_size, Color thumb_color, Color track_color, bool vertical);
|
||||
|
||||
void apply_effects(float opacity = 1.0f, Gfx::CompositingAndBlendingOperator = Gfx::CompositingAndBlendingOperator::Normal, Optional<Gfx::Filter> filter = {}, Optional<Gfx::MaskKind> mask_kind = {});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibWeb/HTML/HTMLHtmlElement.h>
|
||||
#include <LibWeb/HTML/Navigable.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/BackgroundPainting.h>
|
||||
#include <LibWeb/Painting/ChromeMetrics.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
|
|
@ -532,7 +533,7 @@ Optional<PaintableBox::ScrollbarData> PaintableBox::compute_scrollbar_data(Scrol
|
|||
scrollbar_data.thumb_travel_to_scroll_ratio = (usable_scrollbar_length - thumb_length) / (scrollable_overflow_length - scrollport_size);
|
||||
|
||||
if (scroll_state_snapshot) {
|
||||
auto own_offset = scroll_state_snapshot->own_offset_for_frame_with_id(own_scroll_frame_id().value());
|
||||
auto own_offset = scroll_state_snapshot->css_offset_for_frame_with_id(own_scroll_frame_id().value());
|
||||
CSSPixels scroll_offset = is_horizontal ? -own_offset.x() : -own_offset.y();
|
||||
CSSPixels thumb_offset = scroll_offset * scrollbar_data.thumb_travel_to_scroll_ratio;
|
||||
|
||||
|
|
@ -608,7 +609,7 @@ void PaintableBox::paint(DisplayListRecordingContext& context, PaintPhase phase)
|
|||
own_scroll_frame_id().value(),
|
||||
context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>(),
|
||||
context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>(),
|
||||
scrollbar_data->thumb_travel_to_scroll_ratio,
|
||||
scrollbar_data->thumb_travel_to_scroll_ratio.to_double(),
|
||||
scrollbar_colors.thumb_color,
|
||||
scrollbar_colors.track_color,
|
||||
direction == ScrollDirection::Vertical);
|
||||
|
|
@ -720,7 +721,7 @@ void PaintableBox::paint_backdrop_filter(DisplayListRecordingContext& context) c
|
|||
auto border_radii_data = normalized_border_radii_data();
|
||||
ScopedCornerRadiusClip corner_clipper { context, backdrop_region, border_radii_data };
|
||||
if (auto resolved_backdrop_filter = to_gfx_filter(m_backdrop_filter, context.device_pixels_per_css_pixel()); resolved_backdrop_filter.has_value())
|
||||
context.display_list_recorder().apply_backdrop_filter(backdrop_region.to_type<int>(), border_radii_data, *resolved_backdrop_filter);
|
||||
context.display_list_recorder().apply_backdrop_filter(backdrop_region.to_type<int>(), border_radii_data.as_corners(context.device_pixel_converter()), *resolved_backdrop_filter);
|
||||
}
|
||||
|
||||
void PaintableBox::paint_background(DisplayListRecordingContext& context) const
|
||||
|
|
@ -782,10 +783,12 @@ CSSPixelPoint PaintableBox::transform_to_local_coordinates(CSSPixelPoint screen_
|
|||
if (!accumulated_visual_context())
|
||||
return screen_position;
|
||||
|
||||
auto const& viewport_paintable = *document().paintable();
|
||||
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
|
||||
auto local_pos = accumulated_visual_context()->transform_point_for_hit_test(screen_position, scroll_state);
|
||||
return local_pos.value_or(screen_position);
|
||||
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
|
||||
auto result = accumulated_visual_context()->transform_point_for_hit_test(screen_position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (!result.has_value())
|
||||
return screen_position;
|
||||
return (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
}
|
||||
|
||||
bool PaintableBox::has_resizer() const
|
||||
|
|
@ -1038,13 +1041,16 @@ TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType typ
|
|||
if (!is_visible || !visible_for_hit_testing())
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto const& viewport_paintable = *document().paintable();
|
||||
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
|
||||
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
|
||||
Optional<CSSPixelPoint> local_position;
|
||||
if (auto state = accumulated_visual_context())
|
||||
local_position = state->transform_point_for_hit_test(position, scroll_state);
|
||||
else
|
||||
if (auto state = accumulated_visual_context()) {
|
||||
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (result.has_value())
|
||||
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
} else {
|
||||
local_position = position;
|
||||
}
|
||||
|
||||
if (!local_position.has_value())
|
||||
return TraversalDecision::Continue;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <LibWeb/HTML/Navigable.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/PaintableWithLines.h>
|
||||
#include <LibWeb/Painting/ShadowPainting.h>
|
||||
|
|
@ -76,6 +77,7 @@ void PaintableWithLines::paint_text_fragment_debug_highlight(DisplayListRecordin
|
|||
TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
|
||||
{
|
||||
auto const is_visible = computed_values().visibility() == CSS::Visibility::Visible;
|
||||
auto pixel_ratio = static_cast<float>(document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = document().paintable()->scroll_state_snapshot();
|
||||
|
||||
Optional<CSSPixelPoint> local_position;
|
||||
|
|
@ -85,10 +87,13 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (exchange(acquired_local_position, true))
|
||||
return;
|
||||
|
||||
if (auto state = accumulated_visual_context())
|
||||
local_position = state->transform_point_for_hit_test(position, scroll_state);
|
||||
else
|
||||
if (auto state = accumulated_visual_context()) {
|
||||
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (result.has_value())
|
||||
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
} else {
|
||||
local_position = position;
|
||||
}
|
||||
};
|
||||
|
||||
// TextCursor hit testing mode should be able to place cursor in contenteditable elements even if they are empty.
|
||||
|
|
@ -137,9 +142,14 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
// Fragments are descendants of this element, so use the descendants' visual context to account for this element's
|
||||
// own scroll offset during fragment hit testing.
|
||||
auto avc_for_descendants = accumulated_visual_context_for_descendants();
|
||||
auto local_position_for_fragments = avc_for_descendants
|
||||
? avc_for_descendants->transform_point_for_hit_test(position, scroll_state)
|
||||
: local_position;
|
||||
Optional<CSSPixelPoint> local_position_for_fragments;
|
||||
if (avc_for_descendants) {
|
||||
auto result = avc_for_descendants->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (result.has_value())
|
||||
local_position_for_fragments = (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
} else {
|
||||
local_position_for_fragments = local_position;
|
||||
}
|
||||
if (local_position_for_fragments.has_value()) {
|
||||
if (hit_test_fragments(position, local_position_for_fragments.value(), type, callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ static RefPtr<DisplayList> paint_mask_or_clip_to_display_list(
|
|||
bool is_clip_path)
|
||||
{
|
||||
auto mask_rect = context.enclosing_device_rect(area);
|
||||
auto display_list = DisplayList::create(context.device_pixels_per_css_pixel());
|
||||
auto display_list = DisplayList::create();
|
||||
DisplayListRecorder display_list_recorder(*display_list);
|
||||
display_list_recorder.translate(-mask_rect.location().to_type<int>());
|
||||
auto paint_context = context.clone(display_list_recorder);
|
||||
|
|
|
|||
|
|
@ -8,12 +8,17 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
ScrollStateSnapshot ScrollStateSnapshot::create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames)
|
||||
ScrollStateSnapshot ScrollStateSnapshot::create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames, double device_pixels_per_css_pixel)
|
||||
{
|
||||
ScrollStateSnapshot snapshot;
|
||||
snapshot.own_offsets.ensure_capacity(scroll_frames.size());
|
||||
for (auto const& scroll_frame : scroll_frames)
|
||||
snapshot.own_offsets.append({ scroll_frame->m_own_offset });
|
||||
auto scale = static_cast<float>(device_pixels_per_css_pixel);
|
||||
snapshot.m_css_offsets.ensure_capacity(scroll_frames.size());
|
||||
snapshot.m_device_offsets.ensure_capacity(scroll_frames.size());
|
||||
for (auto const& scroll_frame : scroll_frames) {
|
||||
auto const& offset = scroll_frame->m_own_offset;
|
||||
snapshot.m_css_offsets.unchecked_append(offset);
|
||||
snapshot.m_device_offsets.unchecked_append(offset.to_type<float>() * scale);
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,23 +6,27 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibGfx/Point.h>
|
||||
#include <LibWeb/Painting/ScrollFrame.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
class ScrollStateSnapshot {
|
||||
public:
|
||||
static ScrollStateSnapshot create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames);
|
||||
static ScrollStateSnapshot create(Vector<NonnullRefPtr<ScrollFrame>> const& scroll_frames, double device_pixels_per_css_pixel);
|
||||
|
||||
CSSPixelPoint own_offset_for_frame_with_id(size_t id) const
|
||||
ReadonlySpan<Gfx::FloatPoint> device_offsets() const { return m_device_offsets; }
|
||||
|
||||
CSSPixelPoint css_offset_for_frame_with_id(size_t id) const
|
||||
{
|
||||
if (id >= own_offsets.size())
|
||||
if (id >= m_css_offsets.size())
|
||||
return {};
|
||||
return own_offsets[id];
|
||||
return m_css_offsets[id];
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<CSSPixelPoint> own_offsets;
|
||||
Vector<CSSPixelPoint> m_css_offsets;
|
||||
Vector<Gfx::FloatPoint> m_device_offsets;
|
||||
};
|
||||
|
||||
class ScrollState {
|
||||
|
|
@ -69,9 +73,9 @@ public:
|
|||
private:
|
||||
friend class ViewportPaintable;
|
||||
|
||||
ScrollStateSnapshot snapshot() const
|
||||
ScrollStateSnapshot snapshot(double device_pixels_per_css_pixel) const
|
||||
{
|
||||
return ScrollStateSnapshot::create(m_scroll_frames);
|
||||
return ScrollStateSnapshot::create(m_scroll_frames, device_pixels_per_css_pixel);
|
||||
}
|
||||
|
||||
Vector<NonnullRefPtr<ScrollFrame>> m_scroll_frames;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Layout/ReplacedBox.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/DisplayList.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
|
|
@ -313,7 +314,7 @@ void StackingContext::paint(DisplayListRecordingContext& context) const
|
|||
Vector<DisplayListRecorder::MaskInfo> masks;
|
||||
|
||||
if (mask_image) {
|
||||
auto mask_display_list = DisplayList::create(context.device_pixels_per_css_pixel());
|
||||
auto mask_display_list = DisplayList::create();
|
||||
DisplayListRecorder display_list_recorder(*mask_display_list);
|
||||
auto mask_painting_context = context.clone(display_list_recorder);
|
||||
auto mask_rect_in_device_pixels = context.enclosing_device_rect(paintable_box().absolute_padding_box_rect());
|
||||
|
|
@ -391,13 +392,16 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
|
|||
// Hit test the stacking context root's own fragments if it's a PaintableWithLines.
|
||||
if (is<PaintableWithLines>(paintable_box())) {
|
||||
auto const& paintable_with_lines = as<PaintableWithLines>(paintable_box());
|
||||
auto const& viewport_paintable = *paintable_box().document().paintable();
|
||||
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
|
||||
auto pixel_ratio = static_cast<float>(paintable_box().document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = paintable_box().document().paintable()->scroll_state_snapshot();
|
||||
Optional<CSSPixelPoint> local_position;
|
||||
if (auto state = paintable_box().accumulated_visual_context())
|
||||
local_position = state->transform_point_for_hit_test(position, scroll_state);
|
||||
else
|
||||
if (auto state = paintable_box().accumulated_visual_context()) {
|
||||
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (result.has_value())
|
||||
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
} else {
|
||||
local_position = position;
|
||||
}
|
||||
|
||||
if (local_position.has_value()) {
|
||||
if (paintable_with_lines.hit_test_fragments(position, local_position.value(), type, callback) == TraversalDecision::Break)
|
||||
|
|
@ -439,13 +443,16 @@ TraversalDecision StackingContext::hit_test(CSSPixelPoint position, HitTestType
|
|||
if (!is_visible || !paintable_box().visible_for_hit_testing())
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto const& viewport_paintable = *paintable_box().document().paintable();
|
||||
auto const& scroll_state = viewport_paintable.scroll_state_snapshot();
|
||||
auto pixel_ratio = static_cast<float>(paintable_box().document().page().client().device_pixels_per_css_pixel());
|
||||
auto const& scroll_state = paintable_box().document().paintable()->scroll_state_snapshot();
|
||||
Optional<CSSPixelPoint> local_position;
|
||||
if (auto state = paintable_box().accumulated_visual_context())
|
||||
local_position = state->transform_point_for_hit_test(position, scroll_state);
|
||||
else
|
||||
if (auto state = paintable_box().accumulated_visual_context()) {
|
||||
auto result = state->transform_point_for_hit_test(position.to_type<float>() * pixel_ratio, scroll_state.device_offsets());
|
||||
if (result.has_value())
|
||||
local_position = (*result / pixel_ratio).to_type<CSSPixels>();
|
||||
} else {
|
||||
local_position = position;
|
||||
}
|
||||
|
||||
if (local_position.has_value() && paintable_box().absolute_border_box_rect().contains(local_position.value())) {
|
||||
if (callback({ const_cast<PaintableBox&>(paintable_box()) }) == TraversalDecision::Break)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@
|
|||
#include <LibWeb/DOM/Range.h>
|
||||
#include <LibWeb/Layout/TextNode.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/AccumulatedVisualContext.h>
|
||||
#include <LibWeb/Painting/Blending.h>
|
||||
#include <LibWeb/Painting/DevicePixelConverter.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/ResolvedCSSFilter.h>
|
||||
#include <LibWeb/Painting/ScrollFrame.h>
|
||||
#include <LibWeb/Painting/StackingContext.h>
|
||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||
|
|
@ -166,7 +169,22 @@ static CSSPixelRect effective_css_clip_rect(CSSPixelRect const& css_clip)
|
|||
return css_clip;
|
||||
}
|
||||
|
||||
static Optional<TransformData> compute_transform(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values)
|
||||
// Converts a CSS-pixel-space 4x4 matrix to device-pixel-space.
|
||||
// - Translation column (column 3, rows 0-2) is scaled up by DPR
|
||||
// - Perspective row (row 3, columns 0-2) is scaled down by DPR
|
||||
// - All other elements are unaffected (the scale factors cancel out)
|
||||
static FloatMatrix4x4 scale_matrix_for_device_pixels(FloatMatrix4x4 matrix, float scale)
|
||||
{
|
||||
matrix[0, 3] *= scale;
|
||||
matrix[1, 3] *= scale;
|
||||
matrix[2, 3] *= scale;
|
||||
matrix[3, 0] /= scale;
|
||||
matrix[3, 1] /= scale;
|
||||
matrix[3, 2] /= scale;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
static Optional<TransformData> compute_transform(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values, double pixel_ratio)
|
||||
{
|
||||
if (!paintable_box.has_css_transform())
|
||||
return {};
|
||||
|
|
@ -182,9 +200,13 @@ static Optional<TransformData> compute_transform(PaintableBox const& paintable_b
|
|||
matrix = matrix * transform->to_matrix(paintable_box).release_value();
|
||||
auto const& css_transform_origin = computed_values.transform_origin();
|
||||
auto reference_box = paintable_box.transform_reference_box();
|
||||
auto origin_x = reference_box.left() + css_transform_origin.x.to_px(paintable_box.layout_node(), reference_box.width());
|
||||
auto origin_y = reference_box.top() + css_transform_origin.y.to_px(paintable_box.layout_node(), reference_box.height());
|
||||
return TransformData { matrix, { origin_x, origin_y } };
|
||||
CSSPixelPoint origin {
|
||||
reference_box.left() + css_transform_origin.x.to_px(paintable_box.layout_node(), reference_box.width()),
|
||||
reference_box.top() + css_transform_origin.y.to_px(paintable_box.layout_node(), reference_box.height()),
|
||||
};
|
||||
auto scale = static_cast<float>(pixel_ratio);
|
||||
auto device_origin = origin.to_type<float>() * scale;
|
||||
return TransformData { scale_matrix_for_device_pixels(matrix, scale), device_origin };
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-transforms-2/#perspective-matrix
|
||||
|
|
@ -217,7 +239,7 @@ static Optional<Gfx::FloatMatrix4x4> compute_perspective_matrix(PaintableBox con
|
|||
return perspective_matrix;
|
||||
}
|
||||
|
||||
static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values)
|
||||
static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, CSS::ComputedValues const& computed_values, DevicePixelConverter const& converter)
|
||||
{
|
||||
auto overflow_x = computed_values.overflow_x();
|
||||
auto overflow_y = computed_values.overflow_y();
|
||||
|
|
@ -273,7 +295,7 @@ static Optional<ClipData> compute_clip_data(PaintableBox const& paintable_box, C
|
|||
// clipping region is not rounded.
|
||||
// FIXME: Adjust the border radii for the overflow-clip-margin case. (see https://drafts.csswg.org/css-overflow-4/#valdef-overflow-clip-margin-length-0 )
|
||||
auto radii = (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) ? paintable_box.normalized_border_radii_data(PaintableBox::ShrinkRadiiForBorders::Yes) : BorderRadiiData {};
|
||||
return ClipData { clip_rect, radii };
|
||||
return ClipData { converter.rounded_device_rect(clip_rect), radii.as_corners(converter) };
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
@ -283,16 +305,21 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
{
|
||||
m_next_accumulated_visual_context_id = 1;
|
||||
|
||||
auto pixel_ratio = document().page().client().device_pixels_per_css_pixel();
|
||||
DevicePixelConverter converter { pixel_ratio };
|
||||
auto scale = static_cast<float>(pixel_ratio);
|
||||
|
||||
auto append_node = [&](RefPtr<AccumulatedVisualContext const> parent, VisualContextData data) {
|
||||
return AccumulatedVisualContext::create(allocate_accumulated_visual_context_id(), move(data), parent);
|
||||
};
|
||||
|
||||
auto make_effects_data = [](PaintableBox const& box) -> Optional<EffectsData> {
|
||||
auto make_effects_data = [&](PaintableBox const& box) -> Optional<EffectsData> {
|
||||
auto const& computed_values = box.computed_values();
|
||||
auto gfx_filter = to_gfx_filter(box.filter(), pixel_ratio);
|
||||
EffectsData effects {
|
||||
computed_values.opacity(),
|
||||
mix_blend_mode_to_compositing_and_blending_operator(computed_values.mix_blend_mode()),
|
||||
box.filter()
|
||||
move(gfx_filter)
|
||||
};
|
||||
if (!effects.needs_layer())
|
||||
return {};
|
||||
|
|
@ -303,7 +330,8 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
m_visual_viewport_context = nullptr;
|
||||
auto transform = document().visual_viewport()->transform();
|
||||
if (!transform.is_identity()) {
|
||||
m_visual_viewport_context = append_node(nullptr, TransformData { transform.to_matrix(), CSSPixelPoint { 0, 0 } });
|
||||
auto matrix = scale_matrix_for_device_pixels(transform.to_matrix(), scale);
|
||||
m_visual_viewport_context = append_node(nullptr, TransformData { matrix, { 0.f, 0.f } });
|
||||
}
|
||||
|
||||
RefPtr<AccumulatedVisualContext const> viewport_state_for_descendants = m_visual_viewport_context;
|
||||
|
|
@ -364,15 +392,17 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
if (auto effects = make_effects_data(paintable_box); effects.has_value())
|
||||
own_state = append_node(own_state, effects.release_value());
|
||||
|
||||
if (auto transform_data = compute_transform(paintable_box, computed_values); transform_data.has_value()) {
|
||||
if (auto transform_data = compute_transform(paintable_box, computed_values, pixel_ratio); transform_data.has_value()) {
|
||||
paintable_box.set_has_non_invertible_css_transform(!transform_data->matrix.is_invertible());
|
||||
own_state = append_node(own_state, *transform_data);
|
||||
} else {
|
||||
paintable_box.set_has_non_invertible_css_transform(false);
|
||||
}
|
||||
|
||||
if (auto css_clip = paintable_box.get_clip_rect(); css_clip.has_value())
|
||||
own_state = append_node(own_state, ClipData { effective_css_clip_rect(*css_clip), {} });
|
||||
if (auto css_clip = paintable_box.get_clip_rect(); css_clip.has_value()) {
|
||||
auto effective_rect = effective_css_clip_rect(*css_clip);
|
||||
own_state = append_node(own_state, ClipData { converter.rounded_device_rect(effective_rect), {} });
|
||||
}
|
||||
|
||||
// FIXME: Support other geometry boxes. See: https://drafts.fxtf.org/css-masking/#typedef-geometry-box
|
||||
if (auto const& clip_path = computed_values.clip_path(); clip_path.has_value() && clip_path->is_basic_shape()) {
|
||||
|
|
@ -385,7 +415,9 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
[](CSS::Polygon const& polygon) { return polygon.fill_rule; },
|
||||
[](CSS::Path const& path) { return path.fill_rule; },
|
||||
[](auto const&) { return Gfx::WindingRule::Nonzero; });
|
||||
own_state = append_node(own_state, ClipPathData { move(path), masking_area, fill_rule });
|
||||
auto device_path = path.copy_transformed(Gfx::AffineTransform {}.set_scale(scale, scale));
|
||||
auto device_bounding_rect = converter.rounded_device_rect(masking_area);
|
||||
own_state = append_node(own_state, ClipPathData { move(device_path), device_bounding_rect, fill_rule });
|
||||
}
|
||||
|
||||
paintable_box.set_accumulated_visual_context(own_state);
|
||||
|
|
@ -393,10 +425,12 @@ void ViewportPaintable::assign_accumulated_visual_contexts()
|
|||
// Build state for descendants: own state + perspective + clip + scroll.
|
||||
RefPtr<AccumulatedVisualContext const> state_for_descendants = own_state;
|
||||
|
||||
if (auto perspective_matrix = compute_perspective_matrix(paintable_box, computed_values); perspective_matrix.has_value())
|
||||
state_for_descendants = append_node(state_for_descendants, PerspectiveData { *perspective_matrix });
|
||||
if (auto perspective_matrix = compute_perspective_matrix(paintable_box, computed_values); perspective_matrix.has_value()) {
|
||||
auto scaled_matrix = scale_matrix_for_device_pixels(*perspective_matrix, scale);
|
||||
state_for_descendants = append_node(state_for_descendants, PerspectiveData { scaled_matrix });
|
||||
}
|
||||
|
||||
if (auto clip_data = compute_clip_data(paintable_box, computed_values); clip_data.has_value())
|
||||
if (auto clip_data = compute_clip_data(paintable_box, computed_values, converter); clip_data.has_value())
|
||||
state_for_descendants = append_node(state_for_descendants, clip_data.value());
|
||||
|
||||
if (paintable_box.own_scroll_frame()) {
|
||||
|
|
@ -471,7 +505,7 @@ void ViewportPaintable::refresh_scroll_state()
|
|||
scroll_frame->set_own_offset(-scroll_frame->paintable_box().scroll_offset());
|
||||
});
|
||||
|
||||
m_scroll_state_snapshot = m_scroll_state.snapshot();
|
||||
m_scroll_state_snapshot = m_scroll_state.snapshot(document().page().client().device_pixels_per_css_pixel());
|
||||
}
|
||||
|
||||
static void resolve_paint_only_properties_in_subtree(Paintable& root)
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ void PageClient::set_viewport(Web::DevicePixelSize const& size, double device_pi
|
|||
void PageClient::set_zoom_level(double zoom_level)
|
||||
{
|
||||
m_zoom_level = zoom_level;
|
||||
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(m_viewport_size));
|
||||
page().top_level_traversable()->set_viewport_size(page().device_to_css_size(m_viewport_size), Web::InvalidateDisplayList::Yes);
|
||||
}
|
||||
|
||||
void PageClient::set_maximum_frames_per_second(u64 maximum_frames_per_second)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
AccumulatedVisualContext Tree:
|
||||
[2] clip=[23,23 91.328125x150]
|
||||
[2] clip=[23,23 91x150]
|
||||
[3] scroll_frame_id=1 (PaintableWithLines(BlockContainer(anonymous)))
|
||||
[4] clip=[126.328125,23 91.328125x150]
|
||||
[4] clip=[126,23 92x150]
|
||||
[5] scroll_frame_id=2 (PaintableWithLines(BlockContainer(anonymous)))
|
||||
[6] clip=[229.65625,23 91.328125x150]
|
||||
[6] clip=[230,23 91x150]
|
||||
[7] scroll_frame_id=3 (PaintableWithLines(BlockContainer(anonymous)))
|
||||
|
||||
DisplayList:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue