PaintableBox.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. /*
  2. * Copyright (c) 2022-2023, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/GenericShorthands.h>
  8. #include <LibUnicode/CharacterTypes.h>
  9. #include <LibWeb/CSS/SystemColor.h>
  10. #include <LibWeb/DOM/Document.h>
  11. #include <LibWeb/HTML/HTMLHtmlElement.h>
  12. #include <LibWeb/Layout/BlockContainer.h>
  13. #include <LibWeb/Layout/Viewport.h>
  14. #include <LibWeb/Painting/BackgroundPainting.h>
  15. #include <LibWeb/Painting/FilterPainting.h>
  16. #include <LibWeb/Painting/PaintableBox.h>
  17. #include <LibWeb/Painting/StackingContext.h>
  18. #include <LibWeb/Painting/ViewportPaintable.h>
  19. #include <LibWeb/Platform/FontPlugin.h>
  20. namespace Web::Painting {
  21. JS::NonnullGCPtr<PaintableWithLines> PaintableWithLines::create(Layout::BlockContainer const& block_container)
  22. {
  23. return block_container.heap().allocate_without_realm<PaintableWithLines>(block_container);
  24. }
  25. JS::NonnullGCPtr<PaintableBox> PaintableBox::create(Layout::Box const& layout_box)
  26. {
  27. return layout_box.heap().allocate_without_realm<PaintableBox>(layout_box);
  28. }
  29. PaintableBox::PaintableBox(Layout::Box const& layout_box)
  30. : Paintable(layout_box)
  31. {
  32. }
  33. PaintableBox::~PaintableBox()
  34. {
  35. }
  36. bool PaintableBox::is_visible() const
  37. {
  38. return computed_values().visibility() == CSS::Visibility::Visible && computed_values().opacity() != 0;
  39. }
  40. void PaintableBox::invalidate_stacking_context()
  41. {
  42. m_stacking_context = nullptr;
  43. }
  44. PaintableWithLines::PaintableWithLines(Layout::BlockContainer const& layout_box)
  45. : PaintableBox(layout_box)
  46. {
  47. }
  48. PaintableWithLines::~PaintableWithLines()
  49. {
  50. }
  51. CSSPixelPoint PaintableBox::scroll_offset() const
  52. {
  53. auto const& node = layout_node();
  54. if (node.is_generated_for_before_pseudo_element())
  55. return node.pseudo_element_generator()->scroll_offset(DOM::Element::ScrollOffsetFor::PseudoBefore);
  56. if (node.is_generated_for_after_pseudo_element())
  57. return node.pseudo_element_generator()->scroll_offset(DOM::Element::ScrollOffsetFor::PseudoAfter);
  58. if (!(dom_node() && is<DOM::Element>(*dom_node())))
  59. return {};
  60. return static_cast<DOM::Element const*>(dom_node())->scroll_offset(DOM::Element::ScrollOffsetFor::Self);
  61. }
  62. void PaintableBox::set_scroll_offset(CSSPixelPoint offset)
  63. {
  64. // FIXME: If there is horizontal and vertical scroll ignore only part of the new offset
  65. if (offset.y() < 0 || scroll_offset() == offset)
  66. return;
  67. auto& node = layout_node();
  68. if (node.is_generated_for_before_pseudo_element()) {
  69. node.pseudo_element_generator()->set_scroll_offset(DOM::Element::ScrollOffsetFor::PseudoBefore, offset);
  70. } else if (node.is_generated_for_after_pseudo_element()) {
  71. node.pseudo_element_generator()->set_scroll_offset(DOM::Element::ScrollOffsetFor::PseudoAfter, offset);
  72. } else if (is<DOM::Element>(*dom_node())) {
  73. static_cast<DOM::Element*>(dom_node())->set_scroll_offset(DOM::Element::ScrollOffsetFor::Self, offset);
  74. } else {
  75. return;
  76. }
  77. node.set_needs_display();
  78. }
  79. void PaintableBox::scroll_by(int delta_x, int delta_y)
  80. {
  81. auto scrollable_overflow_rect = this->scrollable_overflow_rect();
  82. if (!scrollable_overflow_rect.has_value())
  83. return;
  84. auto max_x_offset = scrollable_overflow_rect->width() - content_size().width();
  85. auto max_y_offset = scrollable_overflow_rect->height() - content_size().height();
  86. auto current_offset = scroll_offset();
  87. auto new_offset_x = clamp(current_offset.x() + delta_x, 0, max_x_offset);
  88. auto new_offset_y = clamp(current_offset.y() + delta_y, 0, max_y_offset);
  89. set_scroll_offset({ new_offset_x, new_offset_y });
  90. }
  91. void PaintableBox::set_offset(CSSPixelPoint offset)
  92. {
  93. m_offset = offset;
  94. }
  95. void PaintableBox::set_content_size(CSSPixelSize size)
  96. {
  97. m_content_size = size;
  98. layout_box().did_set_content_size();
  99. }
  100. CSSPixelPoint PaintableBox::offset() const
  101. {
  102. return m_offset;
  103. }
  104. CSSPixelRect PaintableBox::compute_absolute_rect() const
  105. {
  106. CSSPixelRect rect { offset(), content_size() };
  107. for (auto const* block = containing_block(); block && block->paintable(); block = block->paintable()->containing_block())
  108. rect.translate_by(block->paintable_box()->offset());
  109. return rect;
  110. }
  111. CSSPixelRect PaintableBox::absolute_rect() const
  112. {
  113. if (!m_absolute_rect.has_value())
  114. m_absolute_rect = compute_absolute_rect();
  115. return *m_absolute_rect;
  116. }
  117. CSSPixelRect PaintableBox::compute_absolute_paint_rect() const
  118. {
  119. // FIXME: This likely incomplete:
  120. auto rect = absolute_border_box_rect();
  121. if (has_scrollable_overflow()) {
  122. auto scrollable_overflow_rect = this->scrollable_overflow_rect().value();
  123. if (computed_values().overflow_x() == CSS::Overflow::Visible)
  124. rect.unite_horizontally(scrollable_overflow_rect);
  125. if (computed_values().overflow_y() == CSS::Overflow::Visible)
  126. rect.unite_vertically(scrollable_overflow_rect);
  127. }
  128. auto resolved_box_shadow_data = resolve_box_shadow_data();
  129. for (auto const& shadow : resolved_box_shadow_data) {
  130. if (shadow.placement == ShadowPlacement::Inner)
  131. continue;
  132. auto inflate = shadow.spread_distance + shadow.blur_radius;
  133. auto shadow_rect = rect.inflated(inflate, inflate, inflate, inflate).translated(shadow.offset_x, shadow.offset_y);
  134. rect = rect.united(shadow_rect);
  135. }
  136. return rect;
  137. }
  138. CSSPixelRect PaintableBox::absolute_paint_rect() const
  139. {
  140. if (!m_absolute_paint_rect.has_value())
  141. m_absolute_paint_rect = compute_absolute_paint_rect();
  142. return *m_absolute_paint_rect;
  143. }
  144. StackingContext* PaintableBox::enclosing_stacking_context()
  145. {
  146. for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
  147. if (auto* stacking_context = ancestor->stacking_context_rooted_here())
  148. return const_cast<StackingContext*>(stacking_context);
  149. }
  150. // We should always reach the viewport's stacking context.
  151. VERIFY_NOT_REACHED();
  152. }
  153. Optional<CSSPixelRect> PaintableBox::get_clip_rect() const
  154. {
  155. auto clip = computed_values().clip();
  156. if (clip.is_rect() && layout_box().is_absolutely_positioned()) {
  157. auto border_box = absolute_border_box_rect();
  158. return clip.to_rect().resolved(layout_node(), border_box);
  159. }
  160. return {};
  161. }
  162. void PaintableBox::before_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
  163. {
  164. if (!is_visible())
  165. return;
  166. auto clip_rect = get_clip_rect();
  167. if (clip_rect.has_value()) {
  168. context.recording_painter().save();
  169. context.recording_painter().add_clip_rect(clip_rect->to_type<int>());
  170. }
  171. }
  172. void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhase phase) const
  173. {
  174. if (!is_visible())
  175. return;
  176. if (get_clip_rect().has_value())
  177. context.recording_painter().restore();
  178. }
  179. void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
  180. {
  181. if (!is_visible())
  182. return;
  183. if (phase == PaintPhase::Background) {
  184. paint_backdrop_filter(context);
  185. paint_background(context);
  186. paint_box_shadow(context);
  187. }
  188. if (phase == PaintPhase::Border) {
  189. paint_border(context);
  190. }
  191. if (phase == PaintPhase::Outline) {
  192. auto outline_width = computed_values().outline_width().to_px(layout_node());
  193. auto borders_data = borders_data_for_outline(layout_node(), computed_values().outline_color(), computed_values().outline_style(), outline_width);
  194. if (borders_data.has_value()) {
  195. auto outline_offset = computed_values().outline_offset().to_px(layout_node());
  196. auto border_radius_data = normalized_border_radii_data(ShrinkRadiiForBorders::No);
  197. auto borders_rect = absolute_border_box_rect();
  198. auto outline_offset_x = outline_offset;
  199. auto outline_offset_y = outline_offset;
  200. // "Both the height and the width of the outside of the shape drawn by the outline should not
  201. // become smaller than twice the computed value of the outline-width property to make sure
  202. // that an outline can be rendered even with large negative values."
  203. // https://www.w3.org/TR/css-ui-4/#outline-offset
  204. // So, if the horizontal outline offset is > half the borders_rect's width then we set it to that.
  205. // (And the same for y)
  206. if ((borders_rect.width() / 2) + outline_offset_x < 0)
  207. outline_offset_x = -borders_rect.width() / 2;
  208. if ((borders_rect.height() / 2) + outline_offset_y < 0)
  209. outline_offset_y = -borders_rect.height() / 2;
  210. border_radius_data.inflate(outline_width + outline_offset_y, outline_width + outline_offset_x, outline_width + outline_offset_y, outline_width + outline_offset_x);
  211. borders_rect.inflate(outline_width + outline_offset_y, outline_width + outline_offset_x, outline_width + outline_offset_y, outline_width + outline_offset_x);
  212. context.recording_painter().paint_borders(context.rounded_device_rect(borders_rect), border_radius_data.as_corners(context), borders_data->to_device_pixels(context));
  213. }
  214. }
  215. if (phase == PaintPhase::Overlay && layout_box().document().inspected_layout_node() == &layout_box()) {
  216. auto content_rect = absolute_rect();
  217. auto margin_box = box_model().margin_box();
  218. CSSPixelRect margin_rect;
  219. margin_rect.set_x(absolute_x() - margin_box.left);
  220. margin_rect.set_width(content_width() + margin_box.left + margin_box.right);
  221. margin_rect.set_y(absolute_y() - margin_box.top);
  222. margin_rect.set_height(content_height() + margin_box.top + margin_box.bottom);
  223. auto border_rect = absolute_border_box_rect();
  224. auto padding_rect = absolute_padding_box_rect();
  225. auto paint_inspector_rect = [&](CSSPixelRect const& rect, Color color) {
  226. auto device_rect = context.enclosing_device_rect(rect).to_type<int>();
  227. context.recording_painter().fill_rect(device_rect, Color(color).with_alpha(100));
  228. context.recording_painter().draw_rect(device_rect, Color(color));
  229. };
  230. paint_inspector_rect(margin_rect, Color::Yellow);
  231. paint_inspector_rect(padding_rect, Color::Cyan);
  232. paint_inspector_rect(border_rect, Color::Green);
  233. paint_inspector_rect(content_rect, Color::Magenta);
  234. auto& font = Platform::FontPlugin::the().default_font();
  235. StringBuilder builder;
  236. if (layout_box().dom_node())
  237. builder.append(layout_box().dom_node()->debug_description());
  238. else
  239. builder.append(layout_box().debug_description());
  240. builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
  241. auto size_text = builder.to_deprecated_string();
  242. auto size_text_rect = border_rect;
  243. size_text_rect.set_y(border_rect.y() + border_rect.height());
  244. size_text_rect.set_top(size_text_rect.top());
  245. size_text_rect.set_width(CSSPixels::nearest_value_for(font.width(size_text)) + 4);
  246. size_text_rect.set_height(CSSPixels::nearest_value_for(font.pixel_size()) + 4);
  247. auto size_text_device_rect = context.enclosing_device_rect(size_text_rect).to_type<int>();
  248. context.recording_painter().fill_rect(size_text_device_rect, context.palette().color(Gfx::ColorRole::Tooltip));
  249. context.recording_painter().draw_rect(size_text_device_rect, context.palette().threed_shadow1());
  250. context.recording_painter().draw_text(size_text_device_rect, size_text, font, Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
  251. }
  252. }
  253. BordersData PaintableBox::remove_element_kind_from_borders_data(PaintableBox::BordersDataWithElementKind borders_data)
  254. {
  255. return {
  256. .top = borders_data.top.border_data,
  257. .right = borders_data.right.border_data,
  258. .bottom = borders_data.bottom.border_data,
  259. .left = borders_data.left.border_data,
  260. };
  261. }
  262. void PaintableBox::paint_border(PaintContext& context) const
  263. {
  264. auto borders_data = m_override_borders_data.has_value() ? remove_element_kind_from_borders_data(m_override_borders_data.value()) : BordersData {
  265. .top = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top(),
  266. .right = box_model().border.right == 0 ? CSS::BorderData() : computed_values().border_right(),
  267. .bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(),
  268. .left = box_model().border.left == 0 ? CSS::BorderData() : computed_values().border_left(),
  269. };
  270. context.recording_painter().paint_borders(context.rounded_device_rect(absolute_border_box_rect()), normalized_border_radii_data().as_corners(context), borders_data.to_device_pixels(context));
  271. }
  272. void PaintableBox::paint_backdrop_filter(PaintContext& context) const
  273. {
  274. auto& backdrop_filter = computed_values().backdrop_filter();
  275. if (!backdrop_filter.is_none())
  276. apply_backdrop_filter(context, absolute_border_box_rect(), normalized_border_radii_data(), backdrop_filter);
  277. }
  278. void PaintableBox::paint_background(PaintContext& context) const
  279. {
  280. // If the body's background properties were propagated to the root element, do no re-paint the body's background.
  281. if (layout_box().is_body() && document().html_element()->should_use_body_background_properties())
  282. return;
  283. CSSPixelRect background_rect;
  284. Color background_color = computed_values().background_color();
  285. auto* background_layers = &computed_values().background_layers();
  286. if (layout_box().is_root_element()) {
  287. // CSS 2.1 Appendix E.2: If the element is a root element, paint the background over the entire canvas.
  288. background_rect = context.css_viewport_rect();
  289. // Section 2.11.2: If the computed value of background-image on the root element is none and its background-color is transparent,
  290. // user agents must instead propagate the computed values of the background properties from that element’s first HTML BODY child element.
  291. if (document().html_element()->should_use_body_background_properties()) {
  292. background_layers = document().background_layers();
  293. background_color = document().background_color();
  294. }
  295. } else {
  296. background_rect = absolute_padding_box_rect();
  297. }
  298. // HACK: If the Box has a border, use the bordered_rect to paint the background.
  299. // This way if we have a border-radius there will be no gap between the filling and actual border.
  300. 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)
  301. background_rect = absolute_border_box_rect();
  302. Painting::paint_background(context, layout_box(), background_rect, background_color, computed_values().image_rendering(), background_layers, normalized_border_radii_data());
  303. }
  304. Vector<ShadowData> PaintableBox::resolve_box_shadow_data() const
  305. {
  306. auto box_shadow_data = computed_values().box_shadow();
  307. if (box_shadow_data.is_empty())
  308. return {};
  309. Vector<ShadowData> resolved_box_shadow_data;
  310. resolved_box_shadow_data.ensure_capacity(box_shadow_data.size());
  311. for (auto const& layer : box_shadow_data) {
  312. resolved_box_shadow_data.empend(
  313. layer.color,
  314. layer.offset_x.to_px(layout_box()),
  315. layer.offset_y.to_px(layout_box()),
  316. layer.blur_radius.to_px(layout_box()),
  317. layer.spread_distance.to_px(layout_box()),
  318. layer.placement == CSS::ShadowPlacement::Outer ? ShadowPlacement::Outer : ShadowPlacement::Inner);
  319. }
  320. return resolved_box_shadow_data;
  321. }
  322. void PaintableBox::paint_box_shadow(PaintContext& context) const
  323. {
  324. auto resolved_box_shadow_data = resolve_box_shadow_data();
  325. if (resolved_box_shadow_data.is_empty())
  326. return;
  327. auto borders_data = BordersData {
  328. .top = computed_values().border_top(),
  329. .right = computed_values().border_right(),
  330. .bottom = computed_values().border_bottom(),
  331. .left = computed_values().border_left(),
  332. };
  333. Painting::paint_box_shadow(context, absolute_border_box_rect(), absolute_padding_box_rect(),
  334. borders_data, normalized_border_radii_data(), resolved_box_shadow_data);
  335. }
  336. BorderRadiiData PaintableBox::normalized_border_radii_data(ShrinkRadiiForBorders shrink) const
  337. {
  338. auto border_radii_data = this->border_radii_data();
  339. if (shrink == ShrinkRadiiForBorders::Yes)
  340. border_radii_data.shrink(computed_values().border_top().width, computed_values().border_right().width, computed_values().border_bottom().width, computed_values().border_left().width);
  341. return border_radii_data;
  342. }
  343. Optional<CSSPixelRect> PaintableBox::calculate_overflow_clipped_rect() const
  344. {
  345. if (layout_node().is_viewport()) {
  346. return {};
  347. }
  348. if (!m_clip_rect.has_value()) {
  349. // NOTE: stacking context should not be crossed while aggregating rectangle to
  350. // clip `overflow: hidden` because intersecting rectangles with different
  351. // transforms doesn't make sense
  352. // TODO: figure out if there are cases when stacking context should be
  353. // crossed to calculate correct clip rect
  354. if (!stacking_context() && containing_block() && containing_block()->paintable_box()) {
  355. m_clip_rect = containing_block()->paintable_box()->calculate_overflow_clipped_rect();
  356. }
  357. auto overflow_x = computed_values().overflow_x();
  358. auto overflow_y = computed_values().overflow_y();
  359. if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
  360. if (m_clip_rect.has_value()) {
  361. m_clip_rect->intersect(absolute_padding_box_rect());
  362. } else {
  363. m_clip_rect = absolute_padding_box_rect();
  364. }
  365. }
  366. }
  367. return m_clip_rect;
  368. }
  369. void PaintableBox::before_children_paint(PaintContext& context, PaintPhase) const
  370. {
  371. auto scroll_offset = -this->scroll_offset();
  372. context.translate_scroll_offset_by(scroll_offset);
  373. context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
  374. }
  375. void PaintableBox::after_children_paint(PaintContext& context, PaintPhase) const
  376. {
  377. auto scroll_offset = this->scroll_offset();
  378. context.translate_scroll_offset_by(scroll_offset);
  379. context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
  380. }
  381. void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
  382. {
  383. if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
  384. return;
  385. // FIXME: Support more overflow variations.
  386. auto clip_rect = this->calculate_overflow_clipped_rect();
  387. auto overflow_x = computed_values().overflow_x();
  388. auto overflow_y = computed_values().overflow_y();
  389. auto css_clip_property = get_clip_rect();
  390. if (css_clip_property.has_value()) {
  391. if (clip_rect.has_value())
  392. clip_rect->intersect(css_clip_property.value());
  393. else
  394. clip_rect = css_clip_property.value();
  395. }
  396. if (!clip_rect.has_value())
  397. return;
  398. if (!m_clipping_overflow) {
  399. context.recording_painter().save();
  400. auto scroll_offset = context.scroll_offset();
  401. context.recording_painter().translate({ -context.enclosing_device_pixels(scroll_offset.x()), -context.enclosing_device_pixels(scroll_offset.y()) });
  402. context.recording_painter().add_clip_rect(context.enclosing_device_rect(*clip_rect).to_type<int>());
  403. context.recording_painter().translate({ context.enclosing_device_pixels(scroll_offset.x()), context.enclosing_device_pixels(scroll_offset.y()) });
  404. m_clipping_overflow = true;
  405. }
  406. if (!clip_rect->is_empty() && overflow_y == CSS::Overflow::Hidden && overflow_x == CSS::Overflow::Hidden) {
  407. auto border_radii_data = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
  408. CornerRadii corner_radii {
  409. .top_left = border_radii_data.top_left.as_corner(context),
  410. .top_right = border_radii_data.top_right.as_corner(context),
  411. .bottom_right = border_radii_data.bottom_right.as_corner(context),
  412. .bottom_left = border_radii_data.bottom_left.as_corner(context)
  413. };
  414. if (border_radii_data.has_any_radius()) {
  415. VERIFY(!m_corner_clipper_id.has_value());
  416. m_corner_clipper_id = context.allocate_corner_clipper_id();
  417. context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_radii, context.recording_painter().state().translation.map(context.rounded_device_rect(*clip_rect).to_type<int>()), CornerClip::Outside);
  418. }
  419. }
  420. }
  421. void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase phase) const
  422. {
  423. if (!AK::first_is_one_of(phase, PaintPhase::Background, PaintPhase::Border, PaintPhase::Foreground))
  424. return;
  425. // FIXME: Support more overflow variations.
  426. if (m_clipping_overflow) {
  427. context.recording_painter().restore();
  428. m_clipping_overflow = false;
  429. }
  430. if (m_corner_clipper_id.has_value()) {
  431. VERIFY(m_corner_clipper_id.has_value());
  432. auto clip_rect = this->calculate_overflow_clipped_rect();
  433. context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.recording_painter().state().translation.map(context.rounded_device_rect(*clip_rect).to_type<int>()));
  434. m_corner_clipper_id = {};
  435. }
  436. }
  437. static void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, Layout::LineBoxFragment const& fragment)
  438. {
  439. auto const& browsing_context = text_node.browsing_context();
  440. if (!browsing_context.is_focused_context())
  441. return;
  442. if (!browsing_context.cursor_blink_state())
  443. return;
  444. if (browsing_context.cursor_position()->node() != &text_node.dom_node())
  445. return;
  446. // 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.
  447. if (browsing_context.cursor_position()->offset() < (unsigned)fragment.start() || browsing_context.cursor_position()->offset() > (unsigned)(fragment.start() + fragment.length()))
  448. return;
  449. if (!fragment.layout_node().dom_node() || !fragment.layout_node().dom_node()->is_editable())
  450. return;
  451. auto fragment_rect = fragment.absolute_rect();
  452. CSSPixelRect cursor_rect {
  453. fragment_rect.x() + CSSPixels::nearest_value_for(text_node.font().width(fragment.text().substring_view(0, text_node.browsing_context().cursor_position()->offset() - fragment.start()))),
  454. fragment_rect.top(),
  455. 1,
  456. fragment_rect.height()
  457. };
  458. auto cursor_device_rect = context.rounded_device_rect(cursor_rect).to_type<int>();
  459. context.recording_painter().draw_rect(cursor_device_rect, text_node.computed_values().color());
  460. }
  461. static void paint_text_decoration(PaintContext& context, Layout::Node const& text_node, Layout::LineBoxFragment const& fragment)
  462. {
  463. auto& painter = context.recording_painter();
  464. auto& font = fragment.layout_node().font();
  465. auto fragment_box = fragment.absolute_rect();
  466. CSSPixels glyph_height = CSSPixels::nearest_value_for(font.pixel_size());
  467. auto baseline = fragment_box.height() / 2 - (glyph_height + 4) / 2 + glyph_height;
  468. auto line_color = text_node.computed_values().text_decoration_color();
  469. CSSPixels css_line_thickness = [&] {
  470. CSS::Length computed_thickness = text_node.computed_values().text_decoration_thickness().resolved(text_node, CSS::Length(1, CSS::Length::Type::Em));
  471. if (computed_thickness.is_auto())
  472. return max(glyph_height.scaled(0.1), 1);
  473. return computed_thickness.to_px(text_node);
  474. }();
  475. auto device_line_thickness = context.rounded_device_pixels(css_line_thickness);
  476. auto text_decoration_lines = text_node.computed_values().text_decoration_line();
  477. for (auto line : text_decoration_lines) {
  478. DevicePixelPoint line_start_point {};
  479. DevicePixelPoint line_end_point {};
  480. switch (line) {
  481. case CSS::TextDecorationLine::None:
  482. return;
  483. case CSS::TextDecorationLine::Underline:
  484. line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline + 2));
  485. line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline + 2));
  486. break;
  487. case CSS::TextDecorationLine::Overline:
  488. line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - glyph_height));
  489. line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline - glyph_height));
  490. break;
  491. case CSS::TextDecorationLine::LineThrough: {
  492. auto x_height = font.x_height();
  493. line_start_point = context.rounded_device_point(fragment_box.top_left().translated(0, baseline - x_height * CSSPixels(0.5f)));
  494. line_end_point = context.rounded_device_point(fragment_box.top_right().translated(-1, baseline - x_height * CSSPixels(0.5f)));
  495. break;
  496. }
  497. case CSS::TextDecorationLine::Blink:
  498. // Conforming user agents may simply not blink the text
  499. return;
  500. }
  501. switch (text_node.computed_values().text_decoration_style()) {
  502. case CSS::TextDecorationStyle::Solid:
  503. painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::Painter::LineStyle::Solid);
  504. break;
  505. case CSS::TextDecorationStyle::Double:
  506. switch (line) {
  507. case CSS::TextDecorationLine::Underline:
  508. break;
  509. case CSS::TextDecorationLine::Overline:
  510. line_start_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
  511. line_end_point.translate_by(0, -device_line_thickness - context.rounded_device_pixels(1));
  512. break;
  513. case CSS::TextDecorationLine::LineThrough:
  514. line_start_point.translate_by(0, -device_line_thickness / 2);
  515. line_end_point.translate_by(0, -device_line_thickness / 2);
  516. break;
  517. default:
  518. VERIFY_NOT_REACHED();
  519. }
  520. painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value());
  521. 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());
  522. break;
  523. case CSS::TextDecorationStyle::Dashed:
  524. painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::Painter::LineStyle::Dashed);
  525. break;
  526. case CSS::TextDecorationStyle::Dotted:
  527. painter.draw_line(line_start_point.to_type<int>(), line_end_point.to_type<int>(), line_color, device_line_thickness.value(), Gfx::Painter::LineStyle::Dotted);
  528. break;
  529. case CSS::TextDecorationStyle::Wavy:
  530. 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());
  531. break;
  532. }
  533. }
  534. }
  535. static void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_node, Layout::LineBoxFragment const& fragment, PaintPhase phase)
  536. {
  537. auto& painter = context.recording_painter();
  538. if (phase == PaintPhase::Foreground) {
  539. auto fragment_absolute_rect = fragment.absolute_rect();
  540. auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
  541. if (text_node.document().inspected_layout_node() == &text_node)
  542. context.recording_painter().draw_rect(fragment_absolute_device_rect.to_type<int>(), Color::Magenta);
  543. auto text = text_node.text_for_rendering();
  544. DevicePixelPoint baseline_start { fragment_absolute_device_rect.x(), fragment_absolute_device_rect.y() + context.rounded_device_pixels(fragment.baseline()) };
  545. auto const& scaled_font = fragment.layout_node().scaled_font(context);
  546. Vector<Gfx::DrawGlyphOrEmoji> scaled_glyph_run;
  547. scaled_glyph_run.ensure_capacity(fragment.glyph_run().size());
  548. for (auto glyph : fragment.glyph_run()) {
  549. glyph.visit([&](auto& glyph) {
  550. glyph.font = &scaled_font;
  551. glyph.position = glyph.position.scaled(context.device_pixels_per_css_pixel());
  552. });
  553. scaled_glyph_run.append(move(glyph));
  554. }
  555. painter.draw_text_run(baseline_start.to_type<int>(), scaled_glyph_run, text_node.computed_values().color(), fragment_absolute_device_rect.to_type<int>());
  556. auto selection_rect = context.enclosing_device_rect(fragment.selection_rect(text_node.font())).to_type<int>();
  557. if (!selection_rect.is_empty()) {
  558. painter.fill_rect(selection_rect, CSS::SystemColor::highlight());
  559. RecordingPainterStateSaver saver(painter);
  560. painter.add_clip_rect(selection_rect);
  561. painter.draw_text_run(baseline_start.to_type<int>(), scaled_glyph_run, CSS::SystemColor::highlight_text(), fragment_absolute_device_rect.to_type<int>());
  562. }
  563. paint_text_decoration(context, text_node, fragment);
  564. paint_cursor_if_needed(context, text_node, fragment);
  565. }
  566. }
  567. void PaintableWithLines::paint(PaintContext& context, PaintPhase phase) const
  568. {
  569. if (!is_visible())
  570. return;
  571. PaintableBox::paint(context, phase);
  572. if (m_line_boxes.is_empty())
  573. return;
  574. bool should_clip_overflow = computed_values().overflow_x() != CSS::Overflow::Visible && computed_values().overflow_y() != CSS::Overflow::Visible;
  575. Optional<u32> corner_clip_id;
  576. auto clip_box = context.rounded_device_rect(absolute_padding_box_rect());
  577. auto border_radius_clip_rect = context.recording_painter().state().translation.map(clip_box.to_type<int>());
  578. if (should_clip_overflow) {
  579. context.recording_painter().save();
  580. // FIXME: Handle overflow-x and overflow-y being different values.
  581. context.recording_painter().add_clip_rect(clip_box.to_type<int>());
  582. auto scroll_offset = context.rounded_device_point(this->scroll_offset());
  583. context.recording_painter().translate(-scroll_offset.to_type<int>());
  584. auto border_radii = normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
  585. CornerRadii corner_radii {
  586. .top_left = border_radii.top_left.as_corner(context),
  587. .top_right = border_radii.top_right.as_corner(context),
  588. .bottom_right = border_radii.bottom_right.as_corner(context),
  589. .bottom_left = border_radii.bottom_left.as_corner(context)
  590. };
  591. if (border_radii.has_any_radius()) {
  592. corner_clip_id = context.allocate_corner_clipper_id();
  593. context.recording_painter().sample_under_corners(*corner_clip_id, corner_radii, border_radius_clip_rect, CornerClip::Outside);
  594. }
  595. }
  596. // Text shadows
  597. // This is yet another loop, but done here because all shadows should appear under all text.
  598. // So, we paint the shadows before painting any text.
  599. // FIXME: Find a smarter way to do this?
  600. if (phase == PaintPhase::Foreground) {
  601. for (auto& line_box : m_line_boxes) {
  602. for (auto& fragment : line_box.fragments()) {
  603. if (is<Layout::TextNode>(fragment.layout_node())) {
  604. auto& text_shadow = fragment.layout_node().computed_values().text_shadow();
  605. if (!text_shadow.is_empty()) {
  606. Vector<ShadowData> resolved_shadow_data;
  607. resolved_shadow_data.ensure_capacity(text_shadow.size());
  608. for (auto const& layer : text_shadow) {
  609. resolved_shadow_data.empend(
  610. layer.color,
  611. layer.offset_x.to_px(layout_box()),
  612. layer.offset_y.to_px(layout_box()),
  613. layer.blur_radius.to_px(layout_box()),
  614. layer.spread_distance.to_px(layout_box()),
  615. ShadowPlacement::Outer);
  616. }
  617. context.recording_painter().set_font(fragment.layout_node().font());
  618. paint_text_shadow(context, fragment, resolved_shadow_data);
  619. }
  620. }
  621. }
  622. }
  623. }
  624. for (auto& line_box : m_line_boxes) {
  625. for (auto& fragment : line_box.fragments()) {
  626. auto fragment_absolute_rect = fragment.absolute_rect();
  627. auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect);
  628. if (context.should_show_line_box_borders()) {
  629. context.recording_painter().draw_rect(fragment_absolute_device_rect.to_type<int>(), Color::Green);
  630. context.recording_painter().draw_line(
  631. context.rounded_device_point(fragment_absolute_rect.top_left().translated(0, fragment.baseline())).to_type<int>(),
  632. context.rounded_device_point(fragment_absolute_rect.top_right().translated(-1, fragment.baseline())).to_type<int>(), Color::Red);
  633. }
  634. if (is<Layout::TextNode>(fragment.layout_node()))
  635. paint_text_fragment(context, static_cast<Layout::TextNode const&>(fragment.layout_node()), fragment, phase);
  636. }
  637. }
  638. if (should_clip_overflow) {
  639. context.recording_painter().restore();
  640. if (corner_clip_id.has_value()) {
  641. context.recording_painter().blit_corner_clipping(*corner_clip_id, border_radius_clip_rect);
  642. corner_clip_id = {};
  643. }
  644. }
  645. }
  646. bool PaintableBox::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned, unsigned, int wheel_delta_x, int wheel_delta_y)
  647. {
  648. if (!layout_box().is_user_scrollable())
  649. return false;
  650. scroll_by(wheel_delta_x, wheel_delta_y);
  651. return true;
  652. }
  653. Layout::BlockContainer const& PaintableWithLines::layout_box() const
  654. {
  655. return static_cast<Layout::BlockContainer const&>(PaintableBox::layout_box());
  656. }
  657. Layout::BlockContainer& PaintableWithLines::layout_box()
  658. {
  659. return static_cast<Layout::BlockContainer&>(PaintableBox::layout_box());
  660. }
  661. void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
  662. {
  663. m_stacking_context = move(stacking_context);
  664. }
  665. Optional<HitTestResult> PaintableBox::hit_test(CSSPixelPoint position, HitTestType type) const
  666. {
  667. if (!is_visible())
  668. return {};
  669. if (layout_box().is_viewport()) {
  670. const_cast<ViewportPaintable&>(static_cast<ViewportPaintable const&>(*this)).build_stacking_context_tree_if_needed();
  671. return stacking_context()->hit_test(position, type);
  672. }
  673. if (!absolute_border_box_rect().contains(position.x(), position.y()))
  674. return {};
  675. for (auto* child = first_child(); child; child = child->next_sibling()) {
  676. auto result = child->hit_test(position, type);
  677. if (!result.has_value())
  678. continue;
  679. if (!result->paintable->visible_for_hit_testing())
  680. continue;
  681. return result;
  682. }
  683. if (!visible_for_hit_testing())
  684. return {};
  685. return HitTestResult { const_cast<PaintableBox&>(*this) };
  686. }
  687. Optional<HitTestResult> PaintableWithLines::hit_test(CSSPixelPoint position, HitTestType type) const
  688. {
  689. if (!layout_box().children_are_inline())
  690. return PaintableBox::hit_test(position, type);
  691. Optional<HitTestResult> last_good_candidate;
  692. for (auto& line_box : m_line_boxes) {
  693. for (auto& fragment : line_box.fragments()) {
  694. if (is<Layout::Box>(fragment.layout_node()) && static_cast<Layout::Box const&>(fragment.layout_node()).paintable_box()->stacking_context())
  695. continue;
  696. if (!fragment.layout_node().containing_block()) {
  697. dbgln("FIXME: PaintableWithLines::hit_test(): Missing containing block on {}", fragment.layout_node().debug_description());
  698. continue;
  699. }
  700. auto fragment_absolute_rect = fragment.absolute_rect();
  701. if (fragment_absolute_rect.contains(position)) {
  702. if (is<Layout::BlockContainer>(fragment.layout_node()) && fragment.layout_node().paintable())
  703. return fragment.layout_node().paintable()->hit_test(position, type);
  704. return HitTestResult { const_cast<Paintable&>(const_cast<Paintable&>(*fragment.layout_node().paintable())), fragment.text_index_at(position.x()) };
  705. }
  706. // 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.
  707. // This determines whether the fragment is a good candidate for the position. The last such good fragment is chosen.
  708. // 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.
  709. // We arbitrarily choose to consider the end of the line above and ignore the beginning of the line below.
  710. // If we knew the direction of selection, we could make a better choice.
  711. if (fragment_absolute_rect.bottom() - 1 <= position.y()) { // fully below the fragment
  712. last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() + fragment.length() };
  713. } else if (fragment_absolute_rect.top() <= position.y()) { // vertically within the fragment
  714. if (position.x() < fragment_absolute_rect.left()) { // left of the fragment
  715. if (!last_good_candidate.has_value()) { // first fragment of the line
  716. last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() };
  717. }
  718. } else { // right of the fragment
  719. last_good_candidate = HitTestResult { const_cast<Paintable&>(*fragment.layout_node().paintable()), fragment.start() + fragment.length() };
  720. }
  721. }
  722. }
  723. }
  724. if (type == HitTestType::TextCursor && last_good_candidate.has_value())
  725. return last_good_candidate;
  726. if (is_visible() && absolute_border_box_rect().contains(position.x(), position.y()))
  727. return HitTestResult { const_cast<PaintableWithLines&>(*this) };
  728. return {};
  729. }
  730. }