2022-03-10 23:13:37 +01:00
/*
2023-02-20 18:56:08 +01:00
* Copyright ( c ) 2022 - 2023 , Andreas Kling < kling @ serenityos . org >
2023-08-23 17:18:33 +01:00
* Copyright ( c ) 2022 - 2023 , Sam Atkins < atkinssj @ serenityos . org >
2022-03-10 23:13:37 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-07-19 11:02:56 +01:00
# include <AK/GenericShorthands.h>
2024-03-03 14:59:02 +11:00
# include <LibGfx/Font/ScaledFont.h>
2024-06-05 10:33:18 +02:00
# include <LibGfx/Painter.h>
2022-05-01 22:36:54 +02:00
# include <LibUnicode/CharacterTypes.h>
2023-08-23 17:18:33 +01:00
# include <LibWeb/CSS/SystemColor.h>
2022-03-10 23:13:37 +01:00
# include <LibWeb/DOM/Document.h>
# include <LibWeb/HTML/HTMLHtmlElement.h>
2024-06-04 00:29:32 +03:00
# include <LibWeb/HTML/Window.h>
2022-03-10 23:13:37 +01:00
# include <LibWeb/Layout/BlockContainer.h>
2023-02-25 11:04:29 +01:00
# include <LibWeb/Layout/Viewport.h>
2022-03-10 23:13:37 +01:00
# include <LibWeb/Painting/BackgroundPainting.h>
# include <LibWeb/Painting/PaintableBox.h>
2024-07-22 18:43:01 +03:00
# include <LibWeb/Painting/SVGPaintable.h>
# include <LibWeb/Painting/SVGSVGPaintable.h>
2022-03-11 00:03:28 +01:00
# include <LibWeb/Painting/StackingContext.h>
2024-01-25 19:21:56 +01:00
# include <LibWeb/Painting/TextPaintable.h>
2023-08-19 08:38:51 +02:00
# include <LibWeb/Painting/ViewportPaintable.h>
2022-09-17 21:25:50 +02:00
# include <LibWeb/Platform/FontPlugin.h>
2022-03-10 23:13:37 +01:00
namespace Web : : Painting {
2023-01-11 12:51:49 +01:00
JS : : NonnullGCPtr < PaintableWithLines > PaintableWithLines : : create ( Layout : : BlockContainer const & block_container )
2022-03-10 23:13:37 +01:00
{
2023-01-11 12:51:49 +01:00
return block_container . heap ( ) . allocate_without_realm < PaintableWithLines > ( block_container ) ;
}
JS : : NonnullGCPtr < PaintableBox > PaintableBox : : create ( Layout : : Box const & layout_box )
{
return layout_box . heap ( ) . allocate_without_realm < PaintableBox > ( layout_box ) ;
2022-03-10 23:13:37 +01:00
}
PaintableBox : : PaintableBox ( Layout : : Box const & layout_box )
: Paintable ( layout_box )
{
}
PaintableBox : : ~ PaintableBox ( )
{
}
PaintableWithLines : : PaintableWithLines ( Layout : : BlockContainer const & layout_box )
: PaintableBox ( layout_box )
{
}
PaintableWithLines : : ~ PaintableWithLines ( )
{
}
2023-08-06 20:51:46 +02:00
CSSPixelPoint PaintableBox : : scroll_offset ( ) const
{
2024-06-03 17:53:55 +03:00
if ( is_viewport ( ) ) {
auto navigable = document ( ) . navigable ( ) ;
VERIFY ( navigable ) ;
return navigable - > viewport_scroll_offset ( ) ;
}
2023-08-06 20:51:46 +02:00
auto const & node = layout_node ( ) ;
if ( node . is_generated_for_before_pseudo_element ( ) )
return node . pseudo_element_generator ( ) - > scroll_offset ( DOM : : Element : : ScrollOffsetFor : : PseudoBefore ) ;
if ( node . is_generated_for_after_pseudo_element ( ) )
return node . pseudo_element_generator ( ) - > scroll_offset ( DOM : : Element : : ScrollOffsetFor : : PseudoAfter ) ;
2023-08-06 21:20:00 +02:00
if ( ! ( dom_node ( ) & & is < DOM : : Element > ( * dom_node ( ) ) ) )
2023-08-06 20:51:46 +02:00
return { } ;
return static_cast < DOM : : Element const * > ( dom_node ( ) ) - > scroll_offset ( DOM : : Element : : ScrollOffsetFor : : Self ) ;
}
2023-08-06 21:10:15 +02:00
void PaintableBox : : set_scroll_offset ( CSSPixelPoint offset )
{
2024-02-22 01:27:21 +01:00
auto scrollable_overflow_rect = this - > scrollable_overflow_rect ( ) ;
if ( ! scrollable_overflow_rect . has_value ( ) )
return ;
2024-05-28 13:47:00 +02:00
document ( ) . set_needs_to_refresh_clip_state ( true ) ;
document ( ) . set_needs_to_refresh_scroll_state ( true ) ;
2024-07-23 12:08:21 -03:00
auto padding_rect = absolute_padding_box_rect ( ) ;
auto max_x_offset = max ( scrollable_overflow_rect - > width ( ) - padding_rect . width ( ) , 0 ) ;
auto max_y_offset = max ( scrollable_overflow_rect - > height ( ) - padding_rect . height ( ) , 0 ) ;
2024-07-21 17:18:47 +01:00
2024-02-22 01:27:21 +01:00
offset . set_x ( clamp ( offset . x ( ) , 0 , max_x_offset ) ) ;
offset . set_y ( clamp ( offset . y ( ) , 0 , max_y_offset ) ) ;
2023-08-06 21:10:15 +02:00
// FIXME: If there is horizontal and vertical scroll ignore only part of the new offset
if ( offset . y ( ) < 0 | | scroll_offset ( ) = = offset )
return ;
auto & node = layout_node ( ) ;
if ( node . is_generated_for_before_pseudo_element ( ) ) {
node . pseudo_element_generator ( ) - > set_scroll_offset ( DOM : : Element : : ScrollOffsetFor : : PseudoBefore , offset ) ;
} else if ( node . is_generated_for_after_pseudo_element ( ) ) {
node . pseudo_element_generator ( ) - > set_scroll_offset ( DOM : : Element : : ScrollOffsetFor : : PseudoAfter , offset ) ;
} else if ( is < DOM : : Element > ( * dom_node ( ) ) ) {
static_cast < DOM : : Element * > ( dom_node ( ) ) - > set_scroll_offset ( DOM : : Element : : ScrollOffsetFor : : Self , offset ) ;
} else {
return ;
}
2024-04-22 18:16:14 +02:00
// https://drafts.csswg.org/cssom-view-1/#scrolling-events
// Whenever an element gets scrolled (whether in response to user interaction or by an API),
// the user agent must run these steps:
// 1. Let doc be the element’ s node document.
auto & document = layout_box ( ) . document ( ) ;
// FIXME: 2. If the element is a snap container, run the steps to update snapchanging targets for the element with
// the element’ s eventual snap target in the block axis as newBlockTarget and the element’ s eventual snap
// target in the inline axis as newInlineTarget.
JS : : NonnullGCPtr < DOM : : EventTarget > const event_target = * dom_node ( ) ;
// 3. If the element is already in doc’ s pending scroll event targets, abort these steps.
if ( document . pending_scroll_event_targets ( ) . contains_slow ( event_target ) )
return ;
// 4. Append the element to doc’ s pending scroll event targets.
document . pending_scroll_event_targets ( ) . append ( * layout_box ( ) . dom_node ( ) ) ;
2024-01-14 13:46:52 +01:00
set_needs_display ( ) ;
2023-08-06 21:10:15 +02:00
}
2023-08-06 20:29:29 +02:00
void PaintableBox : : scroll_by ( int delta_x , int delta_y )
{
2024-02-22 01:27:21 +01:00
set_scroll_offset ( scroll_offset ( ) . translated ( delta_x , delta_y ) ) ;
2023-08-06 20:29:29 +02:00
}
2022-10-31 19:46:55 +00:00
void PaintableBox : : set_offset ( CSSPixelPoint offset )
2022-03-10 23:13:37 +01:00
{
m_offset = offset ;
}
2022-10-31 19:46:55 +00:00
void PaintableBox : : set_content_size ( CSSPixelSize size )
2022-03-10 23:13:37 +01:00
{
m_content_size = size ;
LibWeb: Fix iframes flickering on window resize
After finishing layout, iframe layout boxes (FrameBox) get notified
about their new size by LayoutState::commit(). This information is
forwarded to the nested browsing context, where it can be used for
layout of the nested document.
The problem here was that we notified the FrameBox twice. Once when
assigning the used offset to its paintable, and once when assigning its
size. Because the offset was assigned first, we ended up telling the
FrameBox "btw, your size is 0x0". This caused us to throw away all
the layout information we had for the nested document.
We'd then say "actually, your size is 300x200" (or something) but by
then it was already too late, and we had to do a full relayout.
This caused iframes to flicker as every time their containing document
was laid out, we'd nuke the iframe layout and redo it (on a zero timer).
The fix is pleasantly simple: we didn't need to inform the nested
document of its offset in the containing document's layout anyway. Only
its size is relevant. So we can simply remove the first call, which
removes the bogus 0x0 temporary size.
Note that iframes may still flicker if they change size in the
containing document. That's a separate issue that will require more
finesse to solve. However, this fixes a very noticeable common case.
2023-05-15 12:06:40 +02:00
layout_box ( ) . did_set_content_size ( ) ;
2022-03-10 23:13:37 +01:00
}
2023-08-15 15:42:38 +02:00
CSSPixelPoint PaintableBox : : offset ( ) const
2022-03-10 23:13:37 +01:00
{
2023-08-15 15:11:10 +02:00
return m_offset ;
2022-03-10 23:13:37 +01:00
}
2022-10-31 19:46:55 +00:00
CSSPixelRect PaintableBox : : compute_absolute_rect ( ) const
2022-03-24 16:06:43 +01:00
{
2023-08-15 15:42:38 +02:00
CSSPixelRect rect { offset ( ) , content_size ( ) } ;
2024-03-01 15:30:44 +01:00
for ( auto const * block = containing_block ( ) ; block ; block = block - > containing_block ( ) )
rect . translate_by ( block - > offset ( ) ) ;
2022-03-24 16:06:43 +01:00
return rect ;
}
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
CSSPixelRect PaintableBox : : compute_absolute_padding_rect_with_css_transform_applied ( ) const
{
2024-04-28 15:21:24 +02:00
auto rect = absolute_rect ( ) ;
auto scroll_offset = this - > enclosing_scroll_frame_offset ( ) ;
if ( scroll_offset . has_value ( ) )
rect . translate_by ( scroll_offset . value ( ) ) ;
rect . translate_by ( combined_css_transform ( ) . translation ( ) . to_type < CSSPixels > ( ) ) ;
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
CSSPixelRect padding_rect ;
padding_rect . set_x ( rect . x ( ) - box_model ( ) . padding . left ) ;
padding_rect . set_width ( content_width ( ) + box_model ( ) . padding . left + box_model ( ) . padding . right ) ;
padding_rect . set_y ( rect . y ( ) - box_model ( ) . padding . top ) ;
padding_rect . set_height ( content_height ( ) + box_model ( ) . padding . top + box_model ( ) . padding . bottom ) ;
return padding_rect ;
}
2022-10-31 19:46:55 +00:00
CSSPixelRect PaintableBox : : absolute_rect ( ) const
2022-03-10 23:13:37 +01:00
{
2022-03-24 16:06:43 +01:00
if ( ! m_absolute_rect . has_value ( ) )
m_absolute_rect = compute_absolute_rect ( ) ;
2022-03-12 00:35:49 +01:00
return * m_absolute_rect ;
2022-03-10 23:13:37 +01:00
}
2022-10-31 19:46:55 +00:00
CSSPixelRect PaintableBox : : compute_absolute_paint_rect ( ) const
2022-09-25 15:13:31 +01:00
{
// FIXME: This likely incomplete:
auto rect = absolute_border_box_rect ( ) ;
2023-07-12 18:59:44 +02:00
if ( has_scrollable_overflow ( ) ) {
auto scrollable_overflow_rect = this - > scrollable_overflow_rect ( ) . value ( ) ;
if ( computed_values ( ) . overflow_x ( ) = = CSS : : Overflow : : Visible )
rect . unite_horizontally ( scrollable_overflow_rect ) ;
if ( computed_values ( ) . overflow_y ( ) = = CSS : : Overflow : : Visible )
rect . unite_vertically ( scrollable_overflow_rect ) ;
}
2023-12-19 19:42:00 +01:00
for ( auto const & shadow : box_shadow_data ( ) ) {
2022-09-25 15:13:31 +01:00
if ( shadow . placement = = ShadowPlacement : : Inner )
continue ;
2022-10-31 19:46:55 +00:00
auto inflate = shadow . spread_distance + shadow . blur_radius ;
auto shadow_rect = rect . inflated ( inflate , inflate , inflate , inflate ) . translated ( shadow . offset_x , shadow . offset_y ) ;
2022-09-25 15:13:31 +01:00
rect = rect . united ( shadow_rect ) ;
}
return rect ;
}
2022-10-31 19:46:55 +00:00
CSSPixelRect PaintableBox : : absolute_paint_rect ( ) const
2022-09-25 15:13:31 +01:00
{
if ( ! m_absolute_paint_rect . has_value ( ) )
m_absolute_paint_rect = compute_absolute_paint_rect ( ) ;
return * m_absolute_paint_rect ;
}
2023-08-31 17:08:45 -05:00
Optional < CSSPixelRect > PaintableBox : : get_clip_rect ( ) const
{
auto clip = computed_values ( ) . clip ( ) ;
if ( clip . is_rect ( ) & & layout_box ( ) . is_absolutely_positioned ( ) ) {
auto border_box = absolute_border_box_rect ( ) ;
2023-08-31 17:16:39 -05:00
return clip . to_rect ( ) . resolved ( layout_node ( ) , border_box ) ;
2023-08-31 17:08:45 -05:00
}
return { } ;
}
2024-06-04 00:29:32 +03:00
bool PaintableBox : : wants_mouse_events ( ) const
{
if ( scroll_thumb_rect ( ScrollDirection : : Vertical ) . has_value ( ) )
return true ;
if ( scroll_thumb_rect ( ScrollDirection : : Horizontal ) . has_value ( ) )
return true ;
return false ;
}
2023-08-31 17:08:45 -05:00
void PaintableBox : : before_paint ( PaintContext & context , [[maybe_unused]] PaintPhase phase ) const
{
if ( ! is_visible ( ) )
return ;
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
apply_clip_overflow_rect ( context , phase ) ;
2024-02-28 11:54:08 +01:00
apply_scroll_offset ( context , phase ) ;
2023-08-31 17:08:45 -05:00
}
void PaintableBox : : after_paint ( PaintContext & context , [[maybe_unused]] PaintPhase phase ) const
{
if ( ! is_visible ( ) )
return ;
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
reset_scroll_offset ( context , phase ) ;
2024-02-28 11:54:08 +01:00
clear_clip_overflow_rect ( context , phase ) ;
2023-08-31 17:08:45 -05:00
}
2024-02-15 21:12:50 +01:00
bool PaintableBox : : is_scrollable ( ScrollDirection direction ) const
{
auto overflow = direction = = ScrollDirection : : Horizontal ? computed_values ( ) . overflow_x ( ) : computed_values ( ) . overflow_y ( ) ;
auto scrollable_overflow_size = direction = = ScrollDirection : : Horizontal ? scrollable_overflow_rect ( ) - > width ( ) : scrollable_overflow_rect ( ) - > height ( ) ;
auto scrollport_size = direction = = ScrollDirection : : Horizontal ? absolute_padding_box_rect ( ) . width ( ) : absolute_padding_box_rect ( ) . height ( ) ;
2024-06-03 17:53:55 +03:00
if ( is_viewport ( ) | | overflow = = CSS : : Overflow : : Auto )
2024-02-15 21:12:50 +01:00
return scrollable_overflow_size > scrollport_size ;
return overflow = = CSS : : Overflow : : Scroll ;
}
static constexpr CSSPixels scrollbar_thumb_thickness = 8 ;
Optional < CSSPixelRect > PaintableBox : : scroll_thumb_rect ( ScrollDirection direction ) const
{
if ( ! is_scrollable ( direction ) )
return { } ;
auto padding_rect = absolute_padding_box_rect ( ) ;
auto scrollable_overflow_rect = this - > scrollable_overflow_rect ( ) . value ( ) ;
auto scroll_overflow_size = direction = = ScrollDirection : : Horizontal ? scrollable_overflow_rect . width ( ) : scrollable_overflow_rect . height ( ) ;
auto scrollport_size = direction = = ScrollDirection : : Horizontal ? padding_rect . width ( ) : padding_rect . height ( ) ;
auto scroll_offset = direction = = ScrollDirection : : Horizontal ? this - > scroll_offset ( ) . x ( ) : this - > scroll_offset ( ) . y ( ) ;
2024-03-17 07:15:29 +00:00
if ( scroll_overflow_size = = 0 )
return { } ;
2024-02-15 21:12:50 +01:00
auto thumb_size = scrollport_size * ( scrollport_size / scroll_overflow_size ) ;
CSSPixels thumb_position = 0 ;
if ( scroll_overflow_size > scrollport_size )
thumb_position = scroll_offset * ( scrollport_size - thumb_size ) / ( scroll_overflow_size - scrollport_size ) ;
2024-06-03 17:53:55 +03:00
CSSPixelRect thumb_rect ;
2024-02-15 21:12:50 +01:00
if ( direction = = ScrollDirection : : Horizontal ) {
2024-06-03 17:53:55 +03:00
thumb_rect = {
2024-02-15 21:12:50 +01:00
padding_rect . left ( ) + thumb_position ,
padding_rect . bottom ( ) - scrollbar_thumb_thickness ,
thumb_size ,
scrollbar_thumb_thickness
} ;
2024-06-03 17:53:55 +03:00
} else {
thumb_rect = {
padding_rect . right ( ) - scrollbar_thumb_thickness ,
padding_rect . top ( ) + thumb_position ,
scrollbar_thumb_thickness ,
thumb_size
} ;
2024-02-15 21:12:50 +01:00
}
2024-06-03 17:53:55 +03:00
if ( is_viewport ( ) )
thumb_rect . translate_by ( this - > scroll_offset ( ) ) ;
return thumb_rect ;
2024-02-15 21:12:50 +01:00
}
2022-03-10 23:13:37 +01:00
void PaintableBox : : paint ( PaintContext & context , PaintPhase phase ) const
{
if ( ! is_visible ( ) )
return ;
if ( phase = = PaintPhase : : Background ) {
2022-09-15 08:31:32 +01:00
paint_backdrop_filter ( context ) ;
2022-03-10 23:13:37 +01:00
paint_background ( context ) ;
paint_box_shadow ( context ) ;
}
if ( phase = = PaintPhase : : Border ) {
paint_border ( context ) ;
}
2023-08-02 17:24:14 +01:00
if ( phase = = PaintPhase : : Outline ) {
2024-02-11 01:56:39 +01:00
auto const & outline_data = this - > outline_data ( ) ;
if ( outline_data . has_value ( ) ) {
auto outline_offset = this - > outline_offset ( ) ;
2023-08-02 17:24:14 +01:00
auto border_radius_data = normalized_border_radii_data ( ShrinkRadiiForBorders : : No ) ;
2023-08-02 20:09:10 +01:00
auto borders_rect = absolute_border_box_rect ( ) ;
auto outline_offset_x = outline_offset ;
auto outline_offset_y = outline_offset ;
// "Both the height and the width of the outside of the shape drawn by the outline should not
// become smaller than twice the computed value of the outline-width property to make sure
// that an outline can be rendered even with large negative values."
// https://www.w3.org/TR/css-ui-4/#outline-offset
// So, if the horizontal outline offset is > half the borders_rect's width then we set it to that.
// (And the same for y)
if ( ( borders_rect . width ( ) / 2 ) + outline_offset_x < 0 )
outline_offset_x = - borders_rect . width ( ) / 2 ;
if ( ( borders_rect . height ( ) / 2 ) + outline_offset_y < 0 )
outline_offset_y = - borders_rect . height ( ) / 2 ;
2024-02-11 01:56:39 +01:00
border_radius_data . inflate ( outline_data - > top . width + outline_offset_y , outline_data - > right . width + outline_offset_x , outline_data - > bottom . width + outline_offset_y , outline_data - > left . width + outline_offset_x ) ;
borders_rect . inflate ( outline_data - > top . width + outline_offset_y , outline_data - > right . width + outline_offset_x , outline_data - > bottom . width + outline_offset_y , outline_data - > left . width + outline_offset_x ) ;
2023-08-02 20:09:10 +01:00
2024-06-23 18:40:10 +02:00
paint_all_borders ( context . display_list_recorder ( ) , context . rounded_device_rect ( borders_rect ) , border_radius_data . as_corners ( context ) , outline_data - > to_device_pixels ( context ) ) ;
2023-08-02 17:24:14 +01:00
}
}
2024-02-15 21:12:50 +01:00
auto scrollbar_width = computed_values ( ) . scrollbar_width ( ) ;
2024-06-03 17:53:55 +03:00
if ( phase = = PaintPhase : : Overlay & & scrollbar_width ! = CSS : : ScrollbarWidth : : None ) {
2024-02-15 21:12:50 +01:00
auto color = Color ( Color : : NamedColor : : DarkGray ) . with_alpha ( 128 ) ;
2024-07-13 18:01:32 -03:00
auto border_color = Color ( Color : : NamedColor : : LightGray ) . with_alpha ( 128 ) ;
auto borders_data = BordersDataDevicePixels {
. top = BorderDataDevicePixels { border_color , CSS : : LineStyle : : Solid , 1 } ,
. right = BorderDataDevicePixels { border_color , CSS : : LineStyle : : Solid , 1 } ,
. bottom = BorderDataDevicePixels { border_color , CSS : : LineStyle : : Solid , 1 } ,
. left = BorderDataDevicePixels { border_color , CSS : : LineStyle : : Solid , 1 } ,
} ;
2024-02-15 21:12:50 +01:00
int thumb_corner_radius = static_cast < int > ( context . rounded_device_pixels ( scrollbar_thumb_thickness / 2 ) ) ;
2024-07-13 18:01:32 -03:00
CornerRadii corner_radii = {
. top_left = Gfx : : AntiAliasingPainter : : CornerRadius { thumb_corner_radius , thumb_corner_radius } ,
. top_right = Gfx : : AntiAliasingPainter : : CornerRadius { thumb_corner_radius , thumb_corner_radius } ,
. bottom_right = Gfx : : AntiAliasingPainter : : CornerRadius { thumb_corner_radius , thumb_corner_radius } ,
. bottom_left = Gfx : : AntiAliasingPainter : : CornerRadius { thumb_corner_radius , thumb_corner_radius } ,
} ;
2024-02-15 21:12:50 +01:00
if ( auto thumb_rect = scroll_thumb_rect ( ScrollDirection : : Horizontal ) ; thumb_rect . has_value ( ) ) {
auto thumb_device_rect = context . enclosing_device_rect ( thumb_rect . value ( ) ) ;
2024-07-13 18:01:32 -03:00
paint_all_borders ( context . display_list_recorder ( ) , thumb_device_rect , corner_radii , borders_data ) ;
2024-07-13 17:49:22 -03:00
context . display_list_recorder ( ) . fill_rect_with_rounded_corners ( thumb_device_rect . to_type < int > ( ) , color , thumb_corner_radius ) ;
2024-02-15 21:12:50 +01:00
}
if ( auto thumb_rect = scroll_thumb_rect ( ScrollDirection : : Vertical ) ; thumb_rect . has_value ( ) ) {
auto thumb_device_rect = context . enclosing_device_rect ( thumb_rect . value ( ) ) ;
2024-07-13 18:01:32 -03:00
paint_all_borders ( context . display_list_recorder ( ) , thumb_device_rect , corner_radii , borders_data ) ;
2024-07-13 17:49:22 -03:00
context . display_list_recorder ( ) . fill_rect_with_rounded_corners ( thumb_device_rect . to_type < int > ( ) , color , thumb_corner_radius ) ;
2024-02-15 21:12:50 +01:00
}
}
2023-06-02 22:50:11 +02:00
if ( phase = = PaintPhase : : Overlay & & layout_box ( ) . document ( ) . inspected_layout_node ( ) = = & layout_box ( ) ) {
2022-10-31 19:46:55 +00:00
auto content_rect = absolute_rect ( ) ;
2022-03-10 23:13:37 +01:00
auto margin_box = box_model ( ) . margin_box ( ) ;
2022-10-27 16:30:13 +01:00
CSSPixelRect margin_rect ;
2022-03-10 23:13:37 +01:00
margin_rect . set_x ( absolute_x ( ) - margin_box . left ) ;
margin_rect . set_width ( content_width ( ) + margin_box . left + margin_box . right ) ;
margin_rect . set_y ( absolute_y ( ) - margin_box . top ) ;
margin_rect . set_height ( content_height ( ) + margin_box . top + margin_box . bottom ) ;
2022-10-31 19:46:55 +00:00
auto border_rect = absolute_border_box_rect ( ) ;
auto padding_rect = absolute_padding_box_rect ( ) ;
2022-03-10 23:13:37 +01:00
2022-10-27 16:30:13 +01:00
auto paint_inspector_rect = [ & ] ( CSSPixelRect const & rect , Color color ) {
auto device_rect = context . enclosing_device_rect ( rect ) . to_type < int > ( ) ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . fill_rect ( device_rect , Color ( color ) . with_alpha ( 100 ) ) ;
context . display_list_recorder ( ) . draw_rect ( device_rect , Color ( color ) ) ;
2022-03-10 23:13:37 +01:00
} ;
paint_inspector_rect ( margin_rect , Color : : Yellow ) ;
paint_inspector_rect ( padding_rect , Color : : Cyan ) ;
paint_inspector_rect ( border_rect , Color : : Green ) ;
paint_inspector_rect ( content_rect , Color : : Magenta ) ;
2022-09-17 21:25:50 +02:00
auto & font = Platform : : FontPlugin : : the ( ) . default_font ( ) ;
2022-03-29 22:17:25 +02:00
2022-03-10 23:13:37 +01:00
StringBuilder builder ;
if ( layout_box ( ) . dom_node ( ) )
builder . append ( layout_box ( ) . dom_node ( ) - > debug_description ( ) ) ;
else
builder . append ( layout_box ( ) . debug_description ( ) ) ;
builder . appendff ( " {}x{} @ {},{} " , border_rect . width ( ) , border_rect . height ( ) , border_rect . x ( ) , border_rect . y ( ) ) ;
2023-12-27 10:37:13 +01:00
auto size_text = MUST ( builder . to_string ( ) ) ;
2022-03-10 23:13:37 +01:00
auto size_text_rect = border_rect ;
size_text_rect . set_y ( border_rect . y ( ) + border_rect . height ( ) ) ;
size_text_rect . set_top ( size_text_rect . top ( ) ) ;
2023-08-26 15:57:31 +01:00
size_text_rect . set_width ( CSSPixels : : nearest_value_for ( font . width ( size_text ) ) + 4 ) ;
size_text_rect . set_height ( CSSPixels : : nearest_value_for ( font . pixel_size ( ) ) + 4 ) ;
2022-10-27 16:30:13 +01:00
auto size_text_device_rect = context . enclosing_device_rect ( size_text_rect ) . to_type < int > ( ) ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . fill_rect ( size_text_device_rect , context . palette ( ) . color ( Gfx : : ColorRole : : Tooltip ) ) ;
context . display_list_recorder ( ) . draw_rect ( size_text_device_rect , context . palette ( ) . threed_shadow1 ( ) ) ;
context . display_list_recorder ( ) . draw_text ( size_text_device_rect , size_text , font , Gfx : : TextAlignment : : Center , context . palette ( ) . color ( Gfx : : ColorRole : : TooltipText ) ) ;
2022-03-10 23:13:37 +01:00
}
}
2023-07-12 04:20:37 +00:00
BordersData PaintableBox : : remove_element_kind_from_borders_data ( PaintableBox : : BordersDataWithElementKind borders_data )
{
return {
. top = borders_data . top . border_data ,
. right = borders_data . right . border_data ,
. bottom = borders_data . bottom . border_data ,
. left = borders_data . left . border_data ,
} ;
}
2022-03-10 23:13:37 +01:00
void PaintableBox : : paint_border ( PaintContext & context ) const
{
2023-07-12 04:20:37 +00:00
auto borders_data = m_override_borders_data . has_value ( ) ? remove_element_kind_from_borders_data ( m_override_borders_data . value ( ) ) : BordersData {
2023-01-02 23:05:18 +01:00
. top = box_model ( ) . border . top = = 0 ? CSS : : BorderData ( ) : computed_values ( ) . border_top ( ) ,
. right = box_model ( ) . border . right = = 0 ? CSS : : BorderData ( ) : computed_values ( ) . border_right ( ) ,
. bottom = box_model ( ) . border . bottom = = 0 ? CSS : : BorderData ( ) : computed_values ( ) . border_bottom ( ) ,
. left = box_model ( ) . border . left = = 0 ? CSS : : BorderData ( ) : computed_values ( ) . border_left ( ) ,
2022-03-10 23:13:37 +01:00
} ;
2024-06-23 18:40:10 +02:00
paint_all_borders ( context . display_list_recorder ( ) , context . rounded_device_rect ( absolute_border_box_rect ( ) ) , normalized_border_radii_data ( ) . as_corners ( context ) , borders_data . to_device_pixels ( context ) ) ;
2022-03-10 23:13:37 +01:00
}
2022-09-15 08:31:32 +01:00
void PaintableBox : : paint_backdrop_filter ( PaintContext & context ) const
{
2024-07-29 16:51:15 +03:00
auto const & backdrop_filter = computed_values ( ) . backdrop_filter ( ) ;
if ( backdrop_filter . is_none ( ) ) {
return ;
}
auto backdrop_region = context . rounded_device_rect ( absolute_border_box_rect ( ) ) ;
auto border_radii_data = normalized_border_radii_data ( ) ;
ScopedCornerRadiusClip corner_clipper { context , backdrop_region , border_radii_data } ;
context . display_list_recorder ( ) . apply_backdrop_filter ( backdrop_region . to_type < int > ( ) , border_radii_data , backdrop_filter ) ;
2022-09-15 08:31:32 +01:00
}
2022-03-10 23:13:37 +01:00
void PaintableBox : : paint_background ( PaintContext & context ) const
{
// If the body's background properties were propagated to the root element, do no re-paint the body's background.
if ( layout_box ( ) . is_body ( ) & & document ( ) . html_element ( ) - > should_use_body_background_properties ( ) )
return ;
2022-10-27 14:48:52 +01:00
CSSPixelRect background_rect ;
2022-03-10 23:13:37 +01:00
Color background_color = computed_values ( ) . background_color ( ) ;
auto * background_layers = & computed_values ( ) . background_layers ( ) ;
if ( layout_box ( ) . is_root_element ( ) ) {
// CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas.
2022-10-27 14:48:52 +01:00
background_rect = context . css_viewport_rect ( ) ;
2022-03-10 23:13:37 +01:00
// Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent,
// user agents must instead propagate the computed values of the background properties from that element’ s first HTML BODY child element.
if ( document ( ) . html_element ( ) - > should_use_body_background_properties ( ) ) {
background_layers = document ( ) . background_layers ( ) ;
2023-04-24 22:38:50 +02:00
background_color = document ( ) . background_color ( ) ;
2022-03-10 23:13:37 +01:00
}
} else {
2022-10-31 19:46:55 +00:00
background_rect = absolute_padding_box_rect ( ) ;
2022-03-10 23:13:37 +01:00
}
// HACK: If the Box has a border, use the bordered_rect to paint the background.
// This way if we have a border-radius there will be no gap between the filling and actual border.
2023-07-30 15:54:57 +01:00
if ( computed_values ( ) . border_top ( ) . width ! = 0 | | computed_values ( ) . border_right ( ) . width ! = 0 | | computed_values ( ) . border_bottom ( ) . width ! = 0 | | computed_values ( ) . border_left ( ) . width ! = 0 )
2022-10-31 19:46:55 +00:00
background_rect = absolute_border_box_rect ( ) ;
2022-03-10 23:13:37 +01:00
2024-04-23 12:07:58 +02:00
Painting : : paint_background ( context , layout_box ( ) , background_rect , background_color , computed_values ( ) . image_rendering ( ) , background_layers , normalized_border_radii_data ( ) ) ;
2022-03-10 23:13:37 +01:00
}
2022-09-25 15:13:31 +01:00
void PaintableBox : : paint_box_shadow ( PaintContext & context ) const
{
2023-12-19 19:42:00 +01:00
auto const & resolved_box_shadow_data = box_shadow_data ( ) ;
2022-09-25 15:13:31 +01:00
if ( resolved_box_shadow_data . is_empty ( ) )
return ;
2023-06-05 02:42:32 +00:00
auto borders_data = BordersData {
. top = computed_values ( ) . border_top ( ) ,
. right = computed_values ( ) . border_right ( ) ,
. bottom = computed_values ( ) . border_bottom ( ) ,
. left = computed_values ( ) . border_left ( ) ,
} ;
Painting : : paint_box_shadow ( context , absolute_border_box_rect ( ) , absolute_padding_box_rect ( ) ,
borders_data , normalized_border_radii_data ( ) , resolved_box_shadow_data ) ;
2022-03-10 23:13:37 +01:00
}
2022-07-26 20:43:53 +01:00
BorderRadiiData PaintableBox : : normalized_border_radii_data ( ShrinkRadiiForBorders shrink ) const
2022-03-10 23:13:37 +01:00
{
2023-12-07 06:16:17 +01:00
auto border_radii_data = this - > border_radii_data ( ) ;
2022-07-26 20:43:53 +01:00
if ( shrink = = ShrinkRadiiForBorders : : Yes )
2023-12-07 06:16:17 +01:00
border_radii_data . shrink ( computed_values ( ) . border_top ( ) . width , computed_values ( ) . border_right ( ) . width , computed_values ( ) . border_bottom ( ) . width , computed_values ( ) . border_left ( ) . width ) ;
return border_radii_data ;
2022-03-10 23:13:37 +01:00
}
2023-12-11 16:47:40 +01:00
void PaintableBox : : apply_scroll_offset ( PaintContext & context , PaintPhase ) const
2023-08-08 15:02:35 +02:00
{
2024-02-08 17:30:07 +01:00
if ( scroll_frame_id ( ) . has_value ( ) ) {
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . save ( ) ;
context . display_list_recorder ( ) . set_scroll_frame_id ( scroll_frame_id ( ) . value ( ) ) ;
2023-12-29 06:10:32 +01:00
}
2023-08-08 15:02:35 +02:00
}
2023-12-11 16:47:40 +01:00
void PaintableBox : : reset_scroll_offset ( PaintContext & context , PaintPhase ) const
2023-08-08 15:02:35 +02:00
{
2024-02-08 17:30:07 +01:00
if ( scroll_frame_id ( ) . has_value ( ) )
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . restore ( ) ;
2023-08-08 15:02:35 +02:00
}
2023-01-25 04:50:14 +03:00
void PaintableBox : : apply_clip_overflow_rect ( PaintContext & context , PaintPhase phase ) const
2022-11-12 00:07:43 +03:00
{
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
if ( ! AK : : first_is_one_of ( phase , PaintPhase : : Background , PaintPhase : : Border , PaintPhase : : Foreground , PaintPhase : : Outline ) )
2022-09-13 20:45:38 +02:00
return ;
2024-02-08 17:30:07 +01:00
if ( clip_rect ( ) . has_value ( ) ) {
auto overflow_clip_rect = clip_rect ( ) . value ( ) ;
2023-05-21 22:03:53 +01:00
m_clipping_overflow = true ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . save ( ) ;
context . display_list_recorder ( ) . add_clip_rect ( context . enclosing_device_rect ( overflow_clip_rect ) . to_type < int > ( ) ) ;
2024-02-10 17:52:57 +01:00
auto const & border_radii_clips = this - > border_radii_clips ( ) ;
2024-04-27 15:03:40 +02:00
auto const & combined_transform = combined_css_transform ( ) ;
2024-02-10 17:52:57 +01:00
for ( size_t corner_clip_index = 0 ; corner_clip_index < border_radii_clips . size ( ) ; + + corner_clip_index ) {
auto const & corner_clip = border_radii_clips [ corner_clip_index ] ;
auto corners = corner_clip . radii . as_corners ( context ) ;
if ( ! corners . has_any_radius ( ) )
continue ;
auto rect = corner_clip . rect . translated ( - combined_transform . translation ( ) . to_type < CSSPixels > ( ) ) ;
2024-07-29 14:13:34 +03:00
context . display_list_recorder ( ) . add_rounded_rect_clip ( corner_clip . radii . as_corners ( context ) , context . rounded_device_rect ( rect ) . to_type < int > ( ) , CornerClip : : Outside ) ;
2022-07-04 20:49:38 +01:00
}
2022-03-10 23:13:37 +01:00
}
}
2023-01-25 04:50:14 +03:00
void PaintableBox : : clear_clip_overflow_rect ( PaintContext & context , PaintPhase phase ) const
2022-03-10 23:13:37 +01:00
{
LibWeb: Move clip rect calculation to happen before painting
With this change, clip rectangles for boxes with hidden overflow or the
clip property are no longer calculated during the recording of painting
commands. Instead, it has moved to the "pre-paint" phase, along with
the assignment of scrolling offsets, and works in the following way:
1. The paintable tree is traversed to collect all paintable boxes that
have hidden overflow or use the CSS clip property. For each of these
boxes, the "final" clip rectangle is calculated by intersecting clip
rectangles in the containing block chain for a box.
2. The paintable tree is traversed another time, and a clip rectangle
is assigned for each paintable box contained by a node with hidden
overflow or the clip property.
This way, clipping becomes much easier during the painting commands
recording phase, as it only concerns the use of already assigned clip
rectangles. The same approach is applied to handle scrolling offsets.
Also, clip rectangle calculation is now implemented more correctly, as
we no longer stop at the stacking context boundary while intersecting
clip rectangles in the containing block chain.
Fixes:
https://github.com/SerenityOS/serenity/issues/22932
https://github.com/SerenityOS/serenity/issues/22883
https://github.com/SerenityOS/serenity/issues/22679
https://github.com/SerenityOS/serenity/issues/22534
2024-01-26 21:47:26 +01:00
if ( ! AK : : first_is_one_of ( phase , PaintPhase : : Background , PaintPhase : : Border , PaintPhase : : Foreground , PaintPhase : : Outline ) )
2022-07-19 11:02:56 +01:00
return ;
2022-07-04 20:49:38 +01:00
if ( m_clipping_overflow ) {
m_clipping_overflow = false ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . restore ( ) ;
2022-07-04 20:49:38 +01:00
}
2022-03-10 23:13:37 +01:00
}
2024-03-18 10:25:57 +01:00
void paint_cursor_if_needed ( PaintContext & context , TextPaintable const & paintable , PaintableFragment const & fragment )
2022-03-16 23:03:18 +01:00
{
2024-04-26 16:59:04 +02:00
auto const & navigable = * paintable . navigable ( ) ;
2022-03-16 23:03:18 +01:00
2024-04-26 16:59:04 +02:00
if ( ! navigable . is_focused ( ) )
2022-03-16 23:03:18 +01:00
return ;
2024-04-26 16:59:04 +02:00
if ( ! navigable . cursor_blink_state ( ) )
2022-03-16 23:03:18 +01:00
return ;
2024-04-26 16:59:04 +02:00
if ( navigable . cursor_position ( ) - > node ( ) ! = paintable . dom_node ( ) )
2022-03-16 23:03:18 +01:00
return ;
// NOTE: This checks if the cursor is before the start or after the end of the fragment. If it is at the end, after all text, it should still be painted.
2024-04-26 16:59:04 +02:00
if ( navigable . cursor_position ( ) - > offset ( ) < ( unsigned ) fragment . start ( ) | | navigable . cursor_position ( ) - > offset ( ) > ( unsigned ) ( fragment . start ( ) + fragment . length ( ) ) )
2022-03-16 23:03:18 +01:00
return ;
if ( ! fragment . layout_node ( ) . dom_node ( ) | | ! fragment . layout_node ( ) . dom_node ( ) - > is_editable ( ) )
return ;
2022-10-31 19:46:55 +00:00
auto fragment_rect = fragment . absolute_rect ( ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
auto text = fragment . string_view ( ) ;
2022-10-27 16:30:13 +01:00
CSSPixelRect cursor_rect {
2024-04-26 16:59:04 +02:00
fragment_rect . x ( ) + CSSPixels : : nearest_value_for ( paintable . layout_node ( ) . first_available_font ( ) . width ( text . substring_view ( 0 , navigable . cursor_position ( ) - > offset ( ) - fragment . start ( ) ) ) ) ,
2022-10-27 16:30:13 +01:00
fragment_rect . top ( ) ,
1 ,
fragment_rect . height ( )
} ;
auto cursor_device_rect = context . rounded_device_rect ( cursor_rect ) . to_type < int > ( ) ;
2022-03-16 23:03:18 +01:00
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . draw_rect ( cursor_device_rect , paintable . computed_values ( ) . color ( ) ) ;
2022-03-16 23:03:18 +01:00
}
2024-03-18 10:25:57 +01:00
void paint_text_decoration ( PaintContext & context , TextPaintable const & paintable , PaintableFragment const & fragment )
2022-03-16 23:03:18 +01:00
{
2024-06-23 18:40:10 +02:00
auto & painter = context . display_list_recorder ( ) ;
2023-12-09 23:42:02 +01:00
auto & font = fragment . layout_node ( ) . first_available_font ( ) ;
2022-10-31 19:46:55 +00:00
auto fragment_box = fragment . absolute_rect ( ) ;
2023-08-26 15:57:31 +01:00
CSSPixels glyph_height = CSSPixels : : nearest_value_for ( font . pixel_size ( ) ) ;
2024-01-25 18:09:38 +01:00
auto baseline = fragment . baseline ( ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
auto line_color = paintable . computed_values ( ) . text_decoration_color ( ) ;
2024-01-25 19:21:56 +01:00
auto const & text_paintable = static_cast < TextPaintable const & > ( fragment . paintable ( ) ) ;
auto device_line_thickness = context . rounded_device_pixels ( text_paintable . text_decoration_thickness ( ) ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
auto text_decoration_lines = paintable . computed_values ( ) . text_decoration_line ( ) ;
2022-04-14 16:22:35 +01:00
for ( auto line : text_decoration_lines ) {
2022-10-27 16:30:13 +01:00
DevicePixelPoint line_start_point { } ;
DevicePixelPoint line_end_point { } ;
2022-04-14 16:22:35 +01:00
switch ( line ) {
case CSS : : TextDecorationLine : : None :
return ;
2022-03-16 23:03:18 +01:00
case CSS : : TextDecorationLine : : Underline :
2022-10-27 16:30:13 +01:00
line_start_point = context . rounded_device_point ( fragment_box . top_left ( ) . translated ( 0 , baseline + 2 ) ) ;
2023-05-22 00:41:18 +02:00
line_end_point = context . rounded_device_point ( fragment_box . top_right ( ) . translated ( - 1 , baseline + 2 ) ) ;
2022-03-16 23:03:18 +01:00
break ;
case CSS : : TextDecorationLine : : Overline :
2022-10-27 16:30:13 +01:00
line_start_point = context . rounded_device_point ( fragment_box . top_left ( ) . translated ( 0 , baseline - glyph_height ) ) ;
2023-05-22 00:41:18 +02:00
line_end_point = context . rounded_device_point ( fragment_box . top_right ( ) . translated ( - 1 , baseline - glyph_height ) ) ;
2022-03-16 23:03:18 +01:00
break ;
2022-04-14 16:22:35 +01:00
case CSS : : TextDecorationLine : : LineThrough : {
auto x_height = font . x_height ( ) ;
2023-08-26 15:03:04 +01:00
line_start_point = context . rounded_device_point ( fragment_box . top_left ( ) . translated ( 0 , baseline - x_height * CSSPixels ( 0.5f ) ) ) ;
line_end_point = context . rounded_device_point ( fragment_box . top_right ( ) . translated ( - 1 , baseline - x_height * CSSPixels ( 0.5f ) ) ) ;
2022-03-16 23:03:18 +01:00
break ;
2022-04-14 16:22:35 +01:00
}
case CSS : : TextDecorationLine : : Blink :
// Conforming user agents may simply not blink the text
return ;
2022-03-16 23:03:18 +01:00
}
2024-03-18 10:25:57 +01:00
switch ( paintable . computed_values ( ) . text_decoration_style ( ) ) {
2022-04-14 16:22:35 +01:00
case CSS : : TextDecorationStyle : : Solid :
2024-06-05 10:25:10 +02:00
painter . draw_line ( line_start_point . to_type < int > ( ) , line_end_point . to_type < int > ( ) , line_color , device_line_thickness . value ( ) , Gfx : : LineStyle : : Solid ) ;
2022-04-14 16:22:35 +01:00
break ;
case CSS : : TextDecorationStyle : : Double :
switch ( line ) {
case CSS : : TextDecorationLine : : Underline :
break ;
case CSS : : TextDecorationLine : : Overline :
2022-10-27 16:30:13 +01:00
line_start_point . translate_by ( 0 , - device_line_thickness - context . rounded_device_pixels ( 1 ) ) ;
line_end_point . translate_by ( 0 , - device_line_thickness - context . rounded_device_pixels ( 1 ) ) ;
2022-04-14 16:22:35 +01:00
break ;
case CSS : : TextDecorationLine : : LineThrough :
2022-10-27 16:30:13 +01:00
line_start_point . translate_by ( 0 , - device_line_thickness / 2 ) ;
line_end_point . translate_by ( 0 , - device_line_thickness / 2 ) ;
2022-04-14 16:22:35 +01:00
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
2022-10-27 16:30:13 +01:00
painter . draw_line ( line_start_point . to_type < int > ( ) , line_end_point . to_type < int > ( ) , line_color , device_line_thickness . value ( ) ) ;
painter . draw_line ( line_start_point . translated ( 0 , device_line_thickness + 1 ) . to_type < int > ( ) , line_end_point . translated ( 0 , device_line_thickness + 1 ) . to_type < int > ( ) , line_color , device_line_thickness . value ( ) ) ;
2022-04-14 16:22:35 +01:00
break ;
case CSS : : TextDecorationStyle : : Dashed :
2024-06-05 10:25:10 +02:00
painter . draw_line ( line_start_point . to_type < int > ( ) , line_end_point . to_type < int > ( ) , line_color , device_line_thickness . value ( ) , Gfx : : LineStyle : : Dashed ) ;
2022-04-14 16:22:35 +01:00
break ;
case CSS : : TextDecorationStyle : : Dotted :
2024-06-05 10:25:10 +02:00
painter . draw_line ( line_start_point . to_type < int > ( ) , line_end_point . to_type < int > ( ) , line_color , device_line_thickness . value ( ) , Gfx : : LineStyle : : Dotted ) ;
2022-04-14 16:22:35 +01:00
break ;
case CSS : : TextDecorationStyle : : Wavy :
2022-10-27 16:30:13 +01:00
painter . draw_triangle_wave ( line_start_point . to_type < int > ( ) , line_end_point . to_type < int > ( ) , line_color , device_line_thickness . value ( ) + 1 , device_line_thickness . value ( ) ) ;
2022-04-14 16:22:35 +01:00
break ;
}
2022-03-16 23:03:18 +01:00
}
}
2024-03-18 10:25:57 +01:00
void paint_text_fragment ( PaintContext & context , TextPaintable const & paintable , PaintableFragment const & fragment , PaintPhase phase )
2022-03-16 23:03:18 +01:00
{
2024-06-23 18:40:10 +02:00
auto & painter = context . display_list_recorder ( ) ;
2022-03-16 23:03:18 +01:00
2023-04-20 16:02:29 +01:00
if ( phase = = PaintPhase : : Foreground ) {
2022-10-31 19:46:55 +00:00
auto fragment_absolute_rect = fragment . absolute_rect ( ) ;
2022-10-27 16:30:13 +01:00
auto fragment_absolute_device_rect = context . enclosing_device_rect ( fragment_absolute_rect ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
if ( paintable . document ( ) . inspected_layout_node ( ) = = & paintable . layout_node ( ) )
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . draw_rect ( fragment_absolute_device_rect . to_type < int > ( ) , Color : : Magenta ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
auto text = paintable . text_for_rendering ( ) ;
2022-03-16 23:03:18 +01:00
2024-06-29 17:14:23 +02:00
auto glyph_run = fragment . glyph_run ( ) ;
if ( ! glyph_run )
return ;
2022-10-31 19:46:55 +00:00
DevicePixelPoint baseline_start { fragment_absolute_device_rect . x ( ) , fragment_absolute_device_rect . y ( ) + context . rounded_device_pixels ( fragment . baseline ( ) ) } ;
2024-03-01 14:14:47 +01:00
auto scale = context . device_pixels_per_css_pixel ( ) ;
2024-07-07 19:41:18 -06:00
painter . draw_text_run ( baseline_start . to_type < int > ( ) , * glyph_run , paintable . computed_values ( ) . webkit_text_fill_color ( ) , fragment_absolute_device_rect . to_type < int > ( ) , scale ) ;
2022-03-16 23:03:18 +01:00
2024-03-18 10:25:57 +01:00
auto selection_rect = context . enclosing_device_rect ( fragment . selection_rect ( paintable . layout_node ( ) . first_available_font ( ) ) ) . to_type < int > ( ) ;
2022-03-16 23:03:18 +01:00
if ( ! selection_rect . is_empty ( ) ) {
2023-08-23 17:18:33 +01:00
painter . fill_rect ( selection_rect , CSS : : SystemColor : : highlight ( ) ) ;
2024-06-23 18:40:10 +02:00
DisplayListRecorderStateSaver saver ( painter ) ;
2022-10-31 19:46:55 +00:00
painter . add_clip_rect ( selection_rect ) ;
2024-06-29 17:14:23 +02:00
painter . draw_text_run ( baseline_start . to_type < int > ( ) , * glyph_run , CSS : : SystemColor : : highlight_text ( ) , fragment_absolute_device_rect . to_type < int > ( ) , scale ) ;
2022-03-16 23:03:18 +01:00
}
2024-03-18 10:25:57 +01:00
paint_text_decoration ( context , paintable , fragment ) ;
paint_cursor_if_needed ( context , paintable , fragment ) ;
2022-03-16 23:03:18 +01:00
}
}
2022-03-10 23:13:37 +01:00
void PaintableWithLines : : paint ( PaintContext & context , PaintPhase phase ) const
{
if ( ! is_visible ( ) )
return ;
PaintableBox : : paint ( context , phase ) ;
2024-01-12 21:25:05 +01:00
if ( fragments ( ) . is_empty ( ) )
2022-03-10 23:13:37 +01:00
return ;
bool should_clip_overflow = computed_values ( ) . overflow_x ( ) ! = CSS : : Overflow : : Visible & & computed_values ( ) . overflow_y ( ) ! = CSS : : Overflow : : Visible ;
2023-12-05 22:30:32 +01:00
Optional < u32 > corner_clip_id ;
2024-02-03 18:25:31 +01:00
auto clip_box = absolute_padding_box_rect ( ) ;
2024-02-08 17:30:07 +01:00
if ( get_clip_rect ( ) . has_value ( ) ) {
clip_box . intersect ( get_clip_rect ( ) . value ( ) ) ;
should_clip_overflow = true ;
}
2022-03-10 23:13:37 +01:00
if ( should_clip_overflow ) {
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . save ( ) ;
2022-03-10 23:13:37 +01:00
// FIXME: Handle overflow-x and overflow-y being different values.
2024-05-26 11:09:43 +01:00
auto clip_box_with_enclosing_scroll_frame_offset = clip_box ;
if ( enclosing_scroll_frame_offset ( ) . has_value ( ) )
clip_box_with_enclosing_scroll_frame_offset . translate_by ( enclosing_scroll_frame_offset ( ) . value ( ) ) ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . add_clip_rect ( context . rounded_device_rect ( clip_box_with_enclosing_scroll_frame_offset ) . to_type < int > ( ) ) ;
2022-07-04 20:49:38 +01:00
2022-07-26 20:43:53 +01:00
auto border_radii = normalized_border_radii_data ( ShrinkRadiiForBorders : : Yes ) ;
2023-10-15 04:27:48 +02:00
CornerRadii corner_radii {
. top_left = border_radii . top_left . as_corner ( context ) ,
. top_right = border_radii . top_right . as_corner ( context ) ,
. bottom_right = border_radii . bottom_right . as_corner ( context ) ,
. bottom_left = border_radii . bottom_left . as_corner ( context )
} ;
2024-01-15 14:23:08 +01:00
if ( corner_radii . has_any_radius ( ) ) {
2024-07-29 14:13:34 +03:00
context . display_list_recorder ( ) . add_rounded_rect_clip ( corner_radii , context . rounded_device_rect ( clip_box ) . to_type < int > ( ) , CornerClip : : Outside ) ;
2022-07-04 20:49:38 +01:00
}
2024-05-25 19:07:58 +01:00
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . save ( ) ;
2024-05-25 19:07:58 +01:00
auto scroll_offset = context . rounded_device_point ( this - > scroll_offset ( ) ) ;
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . translate ( - scroll_offset . to_type < int > ( ) ) ;
2022-03-10 23:13:37 +01:00
}
2022-03-24 15:20:25 +00:00
// Text shadows
// This is yet another loop, but done here because all shadows should appear under all text.
// So, we paint the shadows before painting any text.
// FIXME: Find a smarter way to do this?
if ( phase = = PaintPhase : : Foreground ) {
2024-01-12 21:25:05 +01:00
for ( auto & fragment : fragments ( ) ) {
2024-01-13 10:38:07 +01:00
paint_text_shadow ( context , fragment , fragment . shadows ( ) ) ;
2022-03-24 15:20:25 +00:00
}
}
2024-01-12 21:25:05 +01:00
for ( auto const & fragment : m_fragments ) {
auto fragment_absolute_rect = fragment . absolute_rect ( ) ;
auto fragment_absolute_device_rect = context . enclosing_device_rect ( fragment_absolute_rect ) ;
if ( context . should_show_line_box_borders ( ) ) {
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . draw_rect ( fragment_absolute_device_rect . to_type < int > ( ) , Color : : Green ) ;
context . display_list_recorder ( ) . draw_line (
2024-01-12 21:25:05 +01:00
context . rounded_device_point ( fragment_absolute_rect . top_left ( ) . translated ( 0 , fragment . baseline ( ) ) ) . to_type < int > ( ) ,
context . rounded_device_point ( fragment_absolute_rect . top_right ( ) . translated ( - 1 , fragment . baseline ( ) ) ) . to_type < int > ( ) , Color : : Red ) ;
2022-03-10 23:13:37 +01:00
}
2024-03-18 10:25:57 +01:00
if ( is < TextPaintable > ( fragment . paintable ( ) ) )
paint_text_fragment ( context , static_cast < TextPaintable const & > ( fragment . paintable ( ) ) , fragment , phase ) ;
2022-03-10 23:13:37 +01:00
}
if ( should_clip_overflow ) {
2024-06-23 18:40:10 +02:00
context . display_list_recorder ( ) . restore ( ) ;
context . display_list_recorder ( ) . restore ( ) ;
2022-03-10 23:13:37 +01:00
}
}
2024-06-04 00:29:32 +03:00
Paintable : : DispatchEventOfSameName PaintableBox : : handle_mousedown ( Badge < EventHandler > , CSSPixelPoint position , unsigned , unsigned )
{
auto vertical_scroll_thumb_rect = scroll_thumb_rect ( ScrollDirection : : Vertical ) ;
auto horizontal_scroll_thumb_rect = scroll_thumb_rect ( ScrollDirection : : Horizontal ) ;
if ( vertical_scroll_thumb_rect . has_value ( ) & & vertical_scroll_thumb_rect . value ( ) . contains ( position ) ) {
if ( is_viewport ( ) )
position . translate_by ( - scroll_offset ( ) ) ;
m_last_mouse_tracking_position = position ;
m_scroll_thumb_dragging_direction = ScrollDirection : : Vertical ;
const_cast < HTML : : Navigable & > ( * navigable ( ) ) . event_handler ( ) . set_mouse_event_tracking_paintable ( this ) ;
} else if ( horizontal_scroll_thumb_rect . has_value ( ) & & horizontal_scroll_thumb_rect . value ( ) . contains ( position ) ) {
if ( is_viewport ( ) )
position . translate_by ( - scroll_offset ( ) ) ;
m_last_mouse_tracking_position = position ;
m_scroll_thumb_dragging_direction = ScrollDirection : : Horizontal ;
const_cast < HTML : : Navigable & > ( * navigable ( ) ) . event_handler ( ) . set_mouse_event_tracking_paintable ( this ) ;
}
return Paintable : : DispatchEventOfSameName : : Yes ;
}
Paintable : : DispatchEventOfSameName PaintableBox : : handle_mouseup ( Badge < EventHandler > , CSSPixelPoint , unsigned , unsigned )
{
if ( m_last_mouse_tracking_position . has_value ( ) ) {
m_last_mouse_tracking_position . clear ( ) ;
m_scroll_thumb_dragging_direction . clear ( ) ;
const_cast < HTML : : Navigable & > ( * navigable ( ) ) . event_handler ( ) . set_mouse_event_tracking_paintable ( nullptr ) ;
}
return Paintable : : DispatchEventOfSameName : : Yes ;
}
Paintable : : DispatchEventOfSameName PaintableBox : : handle_mousemove ( Badge < EventHandler > , CSSPixelPoint position , unsigned , unsigned )
{
if ( m_last_mouse_tracking_position . has_value ( ) ) {
if ( is_viewport ( ) )
position . translate_by ( - scroll_offset ( ) ) ;
Gfx : : Point < double > scroll_delta ;
if ( m_scroll_thumb_dragging_direction = = ScrollDirection : : Horizontal )
scroll_delta . set_x ( ( position . x ( ) - m_last_mouse_tracking_position - > x ( ) ) . to_double ( ) ) ;
else
scroll_delta . set_y ( ( position . y ( ) - m_last_mouse_tracking_position - > y ( ) ) . to_double ( ) ) ;
auto padding_rect = absolute_padding_box_rect ( ) ;
auto scrollable_overflow_rect = this - > scrollable_overflow_rect ( ) . value ( ) ;
auto scroll_overflow_size = m_scroll_thumb_dragging_direction = = ScrollDirection : : Horizontal ? scrollable_overflow_rect . width ( ) : scrollable_overflow_rect . height ( ) ;
auto scrollport_size = m_scroll_thumb_dragging_direction = = ScrollDirection : : Horizontal ? padding_rect . width ( ) : padding_rect . height ( ) ;
auto scroll_px_per_mouse_position_delta_px = scroll_overflow_size . to_double ( ) / scrollport_size . to_double ( ) ;
scroll_delta * = scroll_px_per_mouse_position_delta_px ;
if ( is_viewport ( ) ) {
document ( ) . window ( ) - > scroll_by ( scroll_delta . x ( ) , scroll_delta . y ( ) ) ;
} else {
scroll_by ( scroll_delta . x ( ) , scroll_delta . y ( ) ) ;
}
m_last_mouse_tracking_position = position ;
return Paintable : : DispatchEventOfSameName : : No ;
}
return Paintable : : DispatchEventOfSameName : : Yes ;
}
2023-08-06 14:28:23 +02:00
bool PaintableBox : : handle_mousewheel ( Badge < EventHandler > , CSSPixelPoint , unsigned , unsigned , int wheel_delta_x , int wheel_delta_y )
2022-03-10 23:13:37 +01:00
{
2023-08-08 15:43:48 +02:00
if ( ! layout_box ( ) . is_user_scrollable ( ) )
2022-03-10 23:13:37 +01:00
return false ;
2024-05-06 00:21:42 +02:00
// TODO: Vertical and horizontal scroll overflow should be handled seperately.
if ( ! has_scrollable_overflow ( ) )
return false ;
2023-08-06 20:29:29 +02:00
scroll_by ( wheel_delta_x , wheel_delta_y ) ;
2022-03-10 23:13:37 +01:00
return true ;
}
Layout : : BlockContainer const & PaintableWithLines : : layout_box ( ) const
{
return static_cast < Layout : : BlockContainer const & > ( PaintableBox : : layout_box ( ) ) ;
}
Layout : : BlockContainer & PaintableWithLines : : layout_box ( )
{
return static_cast < Layout : : BlockContainer & > ( PaintableBox : : layout_box ( ) ) ;
}
2024-06-04 00:29:32 +03:00
TraversalDecision PaintableBox : : hit_test_scrollbars ( CSSPixelPoint position , Function < TraversalDecision ( HitTestResult ) > const & callback ) const
{
auto vertical_scroll_thumb_rect = scroll_thumb_rect ( ScrollDirection : : Vertical ) ;
if ( vertical_scroll_thumb_rect . has_value ( ) & & vertical_scroll_thumb_rect . value ( ) . contains ( position ) )
return callback ( HitTestResult { const_cast < PaintableBox & > ( * this ) } ) ;
auto horizontal_scroll_thumb_rect = scroll_thumb_rect ( ScrollDirection : : Horizontal ) ;
if ( horizontal_scroll_thumb_rect . has_value ( ) & & horizontal_scroll_thumb_rect . value ( ) . contains ( position ) )
return callback ( HitTestResult { const_cast < PaintableBox & > ( * this ) } ) ;
return TraversalDecision : : Continue ;
}
2024-02-13 21:34:07 +01:00
TraversalDecision PaintableBox : : hit_test ( CSSPixelPoint position , HitTestType type , Function < TraversalDecision ( HitTestResult ) > const & callback ) const
2022-03-11 00:03:28 +01:00
{
2024-01-30 10:02:07 +01:00
if ( clip_rect ( ) . has_value ( ) & & ! clip_rect ( ) - > contains ( position ) )
2024-02-13 21:34:07 +01:00
return TraversalDecision : : Continue ;
2024-01-30 10:02:07 +01:00
2024-01-29 06:28:17 +01:00
auto position_adjusted_by_scroll_offset = position ;
if ( enclosing_scroll_frame_offset ( ) . has_value ( ) )
position_adjusted_by_scroll_offset . translate_by ( - enclosing_scroll_frame_offset ( ) . value ( ) ) ;
2022-03-21 15:43:37 +01:00
if ( ! is_visible ( ) )
2024-02-13 21:34:07 +01:00
return TraversalDecision : : Continue ;
2022-03-21 15:43:37 +01:00
2024-06-04 00:29:32 +03:00
if ( hit_test_scrollbars ( position_adjusted_by_scroll_offset , callback ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
2023-02-25 11:04:29 +01:00
if ( layout_box ( ) . is_viewport ( ) ) {
2024-01-30 09:50:56 +01:00
auto & viewport_paintable = const_cast < ViewportPaintable & > ( static_cast < ViewportPaintable const & > ( * this ) ) ;
viewport_paintable . build_stacking_context_tree_if_needed ( ) ;
2024-02-08 17:30:07 +01:00
viewport_paintable . document ( ) . update_paint_and_hit_testing_properties_if_needed ( ) ;
viewport_paintable . refresh_scroll_state ( ) ;
viewport_paintable . refresh_clip_state ( ) ;
2024-02-13 21:34:07 +01:00
return stacking_context ( ) - > hit_test ( position , type , callback ) ;
2022-03-21 10:56:02 +01:00
}
2022-03-11 00:03:28 +01:00
2024-02-13 21:34:07 +01:00
for ( auto const * child = last_child ( ) ; child ; child = child - > previous_sibling ( ) ) {
auto z_index = child - > computed_values ( ) . z_index ( ) ;
if ( child - > layout_node ( ) . is_positioned ( ) & & z_index . value_or ( 0 ) = = 0 )
2022-11-03 18:43:20 +01:00
continue ;
2024-02-13 21:34:07 +01:00
if ( child - > hit_test ( position , type , callback ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
2022-11-03 18:43:20 +01:00
}
2023-03-18 20:28:40 +01:00
2024-03-05 13:46:03 +01:00
if ( ! absolute_border_box_rect ( ) . contains ( position_adjusted_by_scroll_offset . x ( ) , position_adjusted_by_scroll_offset . y ( ) ) )
return TraversalDecision : : Continue ;
2023-03-18 20:28:40 +01:00
if ( ! visible_for_hit_testing ( ) )
2024-02-13 21:34:07 +01:00
return TraversalDecision : : Continue ;
return callback ( HitTestResult { const_cast < PaintableBox & > ( * this ) } ) ;
}
2023-03-18 20:28:40 +01:00
2024-02-13 21:34:07 +01:00
Optional < HitTestResult > PaintableBox : : hit_test ( CSSPixelPoint position , HitTestType type ) const
{
Optional < HitTestResult > result ;
( void ) PaintableBox : : hit_test ( position , type , [ & ] ( HitTestResult candidate ) {
VERIFY ( ! result . has_value ( ) ) ;
if ( ! candidate . paintable - > visible_for_hit_testing ( ) )
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Continue ;
2024-02-13 21:34:07 +01:00
result = move ( candidate ) ;
2024-05-04 14:47:04 +01:00
return TraversalDecision : : Break ;
2024-02-13 21:34:07 +01:00
} ) ;
return result ;
2022-03-11 00:03:28 +01:00
}
2024-02-13 21:34:07 +01:00
TraversalDecision PaintableWithLines : : hit_test ( CSSPixelPoint position , HitTestType type , Function < TraversalDecision ( HitTestResult ) > const & callback ) const
2022-03-11 00:03:28 +01:00
{
2024-01-30 10:02:07 +01:00
if ( clip_rect ( ) . has_value ( ) & & ! clip_rect ( ) - > contains ( position ) )
2024-02-13 21:34:07 +01:00
return TraversalDecision : : Continue ;
2024-01-30 10:02:07 +01:00
2024-01-29 06:28:17 +01:00
auto position_adjusted_by_scroll_offset = position ;
if ( enclosing_scroll_frame_offset ( ) . has_value ( ) )
position_adjusted_by_scroll_offset . translate_by ( - enclosing_scroll_frame_offset ( ) . value ( ) ) ;
2024-02-13 21:34:07 +01:00
if ( ! layout_box ( ) . children_are_inline ( ) | | m_fragments . is_empty ( ) ) {
return PaintableBox : : hit_test ( position , type , callback ) ;
}
2022-03-11 00:03:28 +01:00
2024-06-04 00:29:32 +03:00
if ( hit_test_scrollbars ( position_adjusted_by_scroll_offset , callback ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
2024-02-13 21:34:07 +01:00
for ( auto const * child = last_child ( ) ; child ; child = child - > previous_sibling ( ) ) {
if ( child - > hit_test ( position , type , callback ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
2024-01-13 13:11:31 +01:00
}
2022-03-21 11:11:05 +01:00
Optional < HitTestResult > last_good_candidate ;
2024-01-12 21:25:05 +01:00
for ( auto const & fragment : fragments ( ) ) {
2024-01-25 15:29:12 +01:00
if ( fragment . paintable ( ) . stacking_context ( ) )
2024-01-12 21:25:05 +01:00
continue ;
auto fragment_absolute_rect = fragment . absolute_rect ( ) ;
2024-01-29 06:28:17 +01:00
if ( fragment_absolute_rect . contains ( position_adjusted_by_scroll_offset ) ) {
2024-02-13 21:34:07 +01:00
if ( fragment . paintable ( ) . hit_test ( position , type , callback ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
HitTestResult hit_test_result { const_cast < Paintable & > ( fragment . paintable ( ) ) , fragment . text_index_at ( position_adjusted_by_scroll_offset . x ( ) ) } ;
if ( callback ( hit_test_result ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
2024-01-12 21:25:05 +01:00
}
2022-11-30 01:51:07 +01:00
2024-01-12 21:25:05 +01:00
// If we reached this point, the position is not within the fragment. However, the fragment start or end might be the place to place the cursor.
// This determines whether the fragment is a good candidate for the position. The last such good fragment is chosen.
// The best candidate is either the end of the line above, the beginning of the line below, or the beginning or end of the current line.
// We arbitrarily choose to consider the end of the line above and ignore the beginning of the line below.
// If we knew the direction of selection, we could make a better choice.
2024-01-29 06:28:17 +01:00
if ( fragment_absolute_rect . bottom ( ) - 1 < = position_adjusted_by_scroll_offset . y ( ) ) { // fully below the fragment
2024-01-25 15:29:12 +01:00
last_good_candidate = HitTestResult { const_cast < Paintable & > ( fragment . paintable ( ) ) , fragment . start ( ) + fragment . length ( ) } ;
2024-01-29 06:28:17 +01:00
} else if ( fragment_absolute_rect . top ( ) < = position_adjusted_by_scroll_offset . y ( ) ) { // vertically within the fragment
if ( position_adjusted_by_scroll_offset . x ( ) < fragment_absolute_rect . left ( ) ) { // left of the fragment
if ( ! last_good_candidate . has_value ( ) ) { // first fragment of the line
2024-01-25 15:29:12 +01:00
last_good_candidate = HitTestResult { const_cast < Paintable & > ( fragment . paintable ( ) ) , fragment . start ( ) } ;
2022-11-30 01:51:07 +01:00
}
2024-01-12 21:25:05 +01:00
} else { // right of the fragment
2024-01-25 15:29:12 +01:00
last_good_candidate = HitTestResult { const_cast < Paintable & > ( fragment . paintable ( ) ) , fragment . start ( ) + fragment . length ( ) } ;
2022-11-30 01:51:07 +01:00
}
2022-03-11 00:03:28 +01:00
}
}
2024-02-13 21:34:07 +01:00
if ( type = = HitTestType : : TextCursor & & last_good_candidate . has_value ( ) ) {
if ( callback ( last_good_candidate . value ( ) ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
}
if ( ! stacking_context ( ) & & is_visible ( ) & & absolute_border_box_rect ( ) . contains ( position_adjusted_by_scroll_offset . x ( ) , position_adjusted_by_scroll_offset . y ( ) ) ) {
if ( callback ( HitTestResult { const_cast < PaintableWithLines & > ( * this ) } ) = = TraversalDecision : : Break )
return TraversalDecision : : Break ;
}
return TraversalDecision : : Continue ;
2022-03-11 00:03:28 +01:00
}
2024-01-14 13:46:52 +01:00
void PaintableBox : : set_needs_display ( ) const
{
if ( auto navigable = this - > navigable ( ) )
navigable - > set_needs_display ( absolute_rect ( ) ) ;
}
2024-05-26 00:28:48 +01:00
Optional < CSSPixelRect > PaintableBox : : get_masking_area ( ) const
{
// FIXME: Support clip-paths with transforms.
if ( ! combined_css_transform ( ) . is_identity_or_translation ( ) )
return { } ;
auto clip_path = computed_values ( ) . clip_path ( ) ;
// FIXME: Support other clip sources.
if ( ! clip_path . has_value ( ) | | ! clip_path - > is_basic_shape ( ) )
return { } ;
// FIXME: Support other geometry boxes. See: https://drafts.fxtf.org/css-masking/#typedef-geometry-box
return absolute_border_box_rect ( ) ;
}
Optional < Gfx : : Bitmap : : MaskKind > PaintableBox : : get_mask_type ( ) const
{
// Always an alpha mask as only basic shapes are supported right now.
return Gfx : : Bitmap : : MaskKind : : Alpha ;
}
RefPtr < Gfx : : Bitmap > PaintableBox : : calculate_mask ( PaintContext & context , CSSPixelRect const & masking_area ) const
{
VERIFY ( computed_values ( ) . clip_path ( ) - > is_basic_shape ( ) ) ;
auto const & basic_shape = computed_values ( ) . clip_path ( ) - > basic_shape ( ) ;
auto path = basic_shape . to_path ( masking_area , layout_node ( ) ) ;
auto device_pixel_scale = context . device_pixels_per_css_pixel ( ) ;
path = path . copy_transformed ( Gfx : : AffineTransform { } . set_scale ( device_pixel_scale , device_pixel_scale ) ) ;
auto mask_rect = context . enclosing_device_rect ( masking_area ) ;
auto maybe_bitmap = Gfx : : Bitmap : : create ( Gfx : : BitmapFormat : : BGRA8888 , mask_rect . size ( ) . to_type < int > ( ) ) ;
if ( maybe_bitmap . is_error ( ) )
return { } ;
auto bitmap = maybe_bitmap . release_value ( ) ;
Gfx : : Painter painter ( * bitmap ) ;
Gfx : : AntiAliasingPainter aa_painter ( painter ) ;
aa_painter . fill_path ( path , Color : : Black ) ;
return bitmap ;
}
2024-07-22 18:43:01 +03:00
void PaintableBox : : resolve_paint_properties ( )
{
auto const & computed_values = this - > computed_values ( ) ;
auto const & layout_node = this - > layout_node ( ) ;
// Border radii
CSSPixelRect const border_rect { 0 , 0 , border_box_width ( ) , border_box_height ( ) } ;
auto const & border_top_left_radius = computed_values . border_top_left_radius ( ) ;
auto const & border_top_right_radius = computed_values . border_top_right_radius ( ) ;
auto const & border_bottom_right_radius = computed_values . border_bottom_right_radius ( ) ;
auto const & border_bottom_left_radius = computed_values . border_bottom_left_radius ( ) ;
auto radii_data = normalize_border_radii_data ( layout_node , border_rect , border_top_left_radius ,
border_top_right_radius , border_bottom_right_radius ,
border_bottom_left_radius ) ;
set_border_radii_data ( radii_data ) ;
// Box shadows
auto const & box_shadow_data = computed_values . box_shadow ( ) ;
Vector < Painting : : ShadowData > resolved_box_shadow_data ;
resolved_box_shadow_data . ensure_capacity ( box_shadow_data . size ( ) ) ;
for ( auto const & layer : box_shadow_data ) {
resolved_box_shadow_data . empend (
layer . color ,
layer . offset_x . to_px ( layout_node ) ,
layer . offset_y . to_px ( layout_node ) ,
layer . blur_radius . to_px ( layout_node ) ,
layer . spread_distance . to_px ( layout_node ) ,
layer . placement = = CSS : : ShadowPlacement : : Outer ? Painting : : ShadowPlacement : : Outer
: Painting : : ShadowPlacement : : Inner ) ;
}
set_box_shadow_data ( move ( resolved_box_shadow_data ) ) ;
auto const & transformations = computed_values . transformations ( ) ;
if ( ! transformations . is_empty ( ) ) {
auto matrix = Gfx : : FloatMatrix4x4 : : identity ( ) ;
for ( auto const & transform : transformations )
matrix = matrix * transform . to_matrix ( * this ) . release_value ( ) ;
set_transform ( matrix ) ;
}
auto const & transform_origin = computed_values . transform_origin ( ) ;
// https://www.w3.org/TR/css-transforms-1/#transform-box
auto transform_box = computed_values . transform_box ( ) ;
// For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
// border-box is stroke-box.
// FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
// And is it correct to use `else` below?
if ( is < Painting : : SVGPaintable > ( * this ) ) {
switch ( transform_box ) {
case CSS : : TransformBox : : ContentBox :
transform_box = CSS : : TransformBox : : FillBox ;
break ;
case CSS : : TransformBox : : BorderBox :
transform_box = CSS : : TransformBox : : StrokeBox ;
break ;
default :
break ;
}
}
// For elements with associated CSS layout box, the used value for fill-box is content-box and for
// stroke-box and view-box is border-box.
else {
switch ( transform_box ) {
case CSS : : TransformBox : : FillBox :
transform_box = CSS : : TransformBox : : ContentBox ;
break ;
case CSS : : TransformBox : : StrokeBox :
case CSS : : TransformBox : : ViewBox :
transform_box = CSS : : TransformBox : : BorderBox ;
break ;
default :
break ;
}
}
CSSPixelRect reference_box = [ & ] ( ) {
switch ( transform_box ) {
case CSS : : TransformBox : : ContentBox :
// Uses the content box as reference box.
// FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
return absolute_rect ( ) ;
case CSS : : TransformBox : : BorderBox :
// Uses the border box as reference box.
// FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
return absolute_border_box_rect ( ) ;
case CSS : : TransformBox : : FillBox :
// Uses the object bounding box as reference box.
// FIXME: For now we're using the content rect as an approximation.
return absolute_rect ( ) ;
case CSS : : TransformBox : : StrokeBox :
// Uses the stroke bounding box as reference box.
// FIXME: For now we're using the border rect as an approximation.
return absolute_border_box_rect ( ) ;
case CSS : : TransformBox : : ViewBox :
// Uses the nearest SVG viewport as reference box.
// FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
// - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
// - The dimension of the reference box is set to the width and height values of the viewBox attribute.
auto * svg_paintable = first_ancestor_of_type < Painting : : SVGSVGPaintable > ( ) ;
if ( ! svg_paintable )
return absolute_border_box_rect ( ) ;
return svg_paintable - > absolute_rect ( ) ;
}
VERIFY_NOT_REACHED ( ) ;
} ( ) ;
auto x = reference_box . left ( ) + transform_origin . x . to_px ( layout_node , reference_box . width ( ) ) ;
auto y = reference_box . top ( ) + transform_origin . y . to_px ( layout_node , reference_box . height ( ) ) ;
set_transform_origin ( { x , y } ) ;
set_transform_origin ( { x , y } ) ;
// Outlines
auto outline_width = computed_values . outline_width ( ) . to_px ( layout_node ) ;
auto outline_data = borders_data_for_outline ( layout_node , computed_values . outline_color ( ) , computed_values . outline_style ( ) , outline_width ) ;
auto outline_offset = computed_values . outline_offset ( ) . to_px ( layout_node ) ;
set_outline_data ( outline_data ) ;
set_outline_offset ( outline_offset ) ;
auto combined_transform = compute_combined_css_transform ( ) ;
set_combined_css_transform ( combined_transform ) ;
}
void PaintableWithLines : : resolve_paint_properties ( )
{
PaintableBox : : resolve_paint_properties ( ) ;
auto const & layout_node = this - > layout_node ( ) ;
for ( auto const & fragment : fragments ( ) ) {
auto const & text_shadow = fragment . m_layout_node - > computed_values ( ) . text_shadow ( ) ;
if ( ! text_shadow . is_empty ( ) ) {
Vector < Painting : : ShadowData > resolved_shadow_data ;
resolved_shadow_data . ensure_capacity ( text_shadow . size ( ) ) ;
for ( auto const & layer : text_shadow ) {
resolved_shadow_data . empend (
layer . color ,
layer . offset_x . to_px ( layout_node ) ,
layer . offset_y . to_px ( layout_node ) ,
layer . blur_radius . to_px ( layout_node ) ,
layer . spread_distance . to_px ( layout_node ) ,
Painting : : ShadowPlacement : : Outer ) ;
}
const_cast < Painting : : PaintableFragment & > ( fragment ) . set_shadows ( move ( resolved_shadow_data ) ) ;
}
}
}
2022-03-10 23:13:37 +01:00
}