ViewportPaintable.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * Copyright (c) 2023, Andreas Kling <kling@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/Layout/Viewport.h>
  7. #include <LibWeb/Painting/SVGPaintable.h>
  8. #include <LibWeb/Painting/SVGSVGPaintable.h>
  9. #include <LibWeb/Painting/StackingContext.h>
  10. #include <LibWeb/Painting/ViewportPaintable.h>
  11. namespace Web::Painting {
  12. JS::NonnullGCPtr<ViewportPaintable> ViewportPaintable::create(Layout::Viewport const& layout_viewport)
  13. {
  14. return layout_viewport.heap().allocate_without_realm<ViewportPaintable>(layout_viewport);
  15. }
  16. ViewportPaintable::ViewportPaintable(Layout::Viewport const& layout_viewport)
  17. : PaintableWithLines(layout_viewport)
  18. {
  19. }
  20. ViewportPaintable::~ViewportPaintable() = default;
  21. void ViewportPaintable::build_stacking_context_tree_if_needed()
  22. {
  23. if (stacking_context())
  24. return;
  25. build_stacking_context_tree();
  26. }
  27. void ViewportPaintable::build_stacking_context_tree()
  28. {
  29. set_stacking_context(make<StackingContext>(*this, nullptr, 0));
  30. size_t index_in_tree_order = 1;
  31. for_each_in_subtree([&](Paintable const& paintable) {
  32. const_cast<Paintable&>(paintable).invalidate_stacking_context();
  33. if (!paintable.layout_node().establishes_stacking_context()) {
  34. VERIFY(!paintable.stacking_context());
  35. return TraversalDecision::Continue;
  36. }
  37. auto* parent_context = const_cast<Paintable&>(paintable).enclosing_stacking_context();
  38. VERIFY(parent_context);
  39. const_cast<Paintable&>(paintable).set_stacking_context(make<Painting::StackingContext>(const_cast<Paintable&>(paintable), parent_context, index_in_tree_order++));
  40. return TraversalDecision::Continue;
  41. });
  42. stacking_context()->sort();
  43. }
  44. void ViewportPaintable::paint_all_phases(PaintContext& context)
  45. {
  46. build_stacking_context_tree_if_needed();
  47. context.recording_painter().translate(-context.device_viewport_rect().location().to_type<int>());
  48. stacking_context()->paint(context);
  49. }
  50. void ViewportPaintable::assign_scroll_frames()
  51. {
  52. int next_id = 0;
  53. for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
  54. if (paintable_box.has_scrollable_overflow()) {
  55. auto scroll_frame = adopt_ref(*new ScrollFrame());
  56. scroll_frame->id = next_id++;
  57. scroll_state.set(&paintable_box, move(scroll_frame));
  58. }
  59. return TraversalDecision::Continue;
  60. });
  61. for_each_in_subtree([&](auto const& paintable) {
  62. for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
  63. auto const& block_paintable_box = *block->paintable_box();
  64. if (auto scroll_frame = scroll_state.get(&block_paintable_box); scroll_frame.has_value()) {
  65. if (paintable.is_paintable_box()) {
  66. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  67. const_cast<PaintableBox&>(paintable_box).set_enclosing_scroll_frame(scroll_frame.value());
  68. } else if (paintable.is_inline_paintable()) {
  69. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  70. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_scroll_frame(scroll_frame.value());
  71. }
  72. break;
  73. }
  74. }
  75. return TraversalDecision::Continue;
  76. });
  77. }
  78. void ViewportPaintable::assign_clip_frames()
  79. {
  80. for_each_in_subtree_of_type<PaintableBox>([&](auto const& paintable_box) {
  81. auto overflow_x = paintable_box.computed_values().overflow_x();
  82. auto overflow_y = paintable_box.computed_values().overflow_y();
  83. auto has_hidden_overflow = overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible;
  84. if (has_hidden_overflow || paintable_box.get_clip_rect().has_value()) {
  85. auto clip_frame = adopt_ref(*new ClipFrame());
  86. clip_state.set(&paintable_box, move(clip_frame));
  87. }
  88. return TraversalDecision::Continue;
  89. });
  90. for_each_in_subtree([&](auto const& paintable) {
  91. for (auto block = paintable.containing_block(); block; block = block->containing_block()) {
  92. auto const& block_paintable_box = *block->paintable_box();
  93. if (auto clip_frame = clip_state.get(&block_paintable_box); clip_frame.has_value()) {
  94. if (paintable.is_paintable_box()) {
  95. auto const& paintable_box = static_cast<PaintableBox const&>(paintable);
  96. const_cast<PaintableBox&>(paintable_box).set_enclosing_clip_frame(clip_frame.value());
  97. } else if (paintable.is_inline_paintable()) {
  98. auto const& inline_paintable = static_cast<InlinePaintable const&>(paintable);
  99. const_cast<InlinePaintable&>(inline_paintable).set_enclosing_clip_frame(clip_frame.value());
  100. }
  101. break;
  102. }
  103. }
  104. return TraversalDecision::Continue;
  105. });
  106. }
  107. void ViewportPaintable::refresh_scroll_state()
  108. {
  109. for (auto& it : scroll_state) {
  110. auto const& paintable_box = *it.key;
  111. auto& scroll_frame = *it.value;
  112. CSSPixelPoint offset;
  113. for (auto const* block = &paintable_box.layout_box(); block; block = block->containing_block()) {
  114. auto const& block_paintable_box = *block->paintable_box();
  115. offset.translate_by(block_paintable_box.scroll_offset());
  116. }
  117. scroll_frame.offset = -offset;
  118. }
  119. }
  120. void ViewportPaintable::refresh_clip_state()
  121. {
  122. for (auto& it : clip_state) {
  123. auto const& paintable_box = *it.key;
  124. auto& clip_frame = *it.value;
  125. auto overflow_x = paintable_box.computed_values().overflow_x();
  126. auto overflow_y = paintable_box.computed_values().overflow_y();
  127. // Start from CSS clip property if it exists.
  128. Optional<CSSPixelRect> clip_rect = paintable_box.get_clip_rect();
  129. if (overflow_x != CSS::Overflow::Visible && overflow_y != CSS::Overflow::Visible) {
  130. auto overflow_clip_rect = paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
  131. for (auto const* block = &paintable_box.layout_box(); !block->is_viewport(); block = block->containing_block()) {
  132. auto const& block_paintable_box = *block->paintable_box();
  133. auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
  134. auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
  135. if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
  136. auto rect = block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
  137. overflow_clip_rect.intersect(rect);
  138. auto border_radii_data = block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
  139. if (border_radii_data.has_any_radius()) {
  140. BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data };
  141. clip_frame.add_border_radii_clip(border_radii_clip);
  142. }
  143. }
  144. if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
  145. overflow_clip_rect.intersect(css_clip_property_rect.value());
  146. }
  147. clip_rect = overflow_clip_rect;
  148. }
  149. clip_frame.set_rect(*clip_rect);
  150. }
  151. }
  152. static Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData top_left_radius, CSS::BorderRadiusData top_right_radius, CSS::BorderRadiusData bottom_right_radius, CSS::BorderRadiusData bottom_left_radius)
  153. {
  154. Painting::BorderRadiusData bottom_left_radius_px {};
  155. Painting::BorderRadiusData bottom_right_radius_px {};
  156. Painting::BorderRadiusData top_left_radius_px {};
  157. Painting::BorderRadiusData top_right_radius_px {};
  158. bottom_left_radius_px.horizontal_radius = bottom_left_radius.horizontal_radius.to_px(node, rect.width());
  159. bottom_right_radius_px.horizontal_radius = bottom_right_radius.horizontal_radius.to_px(node, rect.width());
  160. top_left_radius_px.horizontal_radius = top_left_radius.horizontal_radius.to_px(node, rect.width());
  161. top_right_radius_px.horizontal_radius = top_right_radius.horizontal_radius.to_px(node, rect.width());
  162. bottom_left_radius_px.vertical_radius = bottom_left_radius.vertical_radius.to_px(node, rect.height());
  163. bottom_right_radius_px.vertical_radius = bottom_right_radius.vertical_radius.to_px(node, rect.height());
  164. top_left_radius_px.vertical_radius = top_left_radius.vertical_radius.to_px(node, rect.height());
  165. top_right_radius_px.vertical_radius = top_right_radius.vertical_radius.to_px(node, rect.height());
  166. // Scale overlapping curves according to https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
  167. // Let f = min(Li/Si), where i ∈ {top, right, bottom, left},
  168. // Si is the sum of the two corresponding radii of the corners on side i,
  169. // and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box.
  170. auto l_top = rect.width();
  171. auto l_bottom = l_top;
  172. auto l_left = rect.height();
  173. auto l_right = l_left;
  174. auto s_top = (top_left_radius_px.horizontal_radius + top_right_radius_px.horizontal_radius);
  175. auto s_right = (top_right_radius_px.vertical_radius + bottom_right_radius_px.vertical_radius);
  176. auto s_bottom = (bottom_left_radius_px.horizontal_radius + bottom_right_radius_px.horizontal_radius);
  177. auto s_left = (top_left_radius_px.vertical_radius + bottom_left_radius_px.vertical_radius);
  178. CSSPixelFraction f = 1;
  179. f = (s_top != 0) ? min(f, l_top / s_top) : f;
  180. f = (s_right != 0) ? min(f, l_right / s_right) : f;
  181. f = (s_bottom != 0) ? min(f, l_bottom / s_bottom) : f;
  182. f = (s_left != 0) ? min(f, l_left / s_left) : f;
  183. // If f < 1, then all corner radii are reduced by multiplying them by f.
  184. if (f < 1) {
  185. top_left_radius_px.horizontal_radius *= f;
  186. top_left_radius_px.vertical_radius *= f;
  187. top_right_radius_px.horizontal_radius *= f;
  188. top_right_radius_px.vertical_radius *= f;
  189. bottom_right_radius_px.horizontal_radius *= f;
  190. bottom_right_radius_px.vertical_radius *= f;
  191. bottom_left_radius_px.horizontal_radius *= f;
  192. bottom_left_radius_px.vertical_radius *= f;
  193. }
  194. return Painting::BorderRadiiData { top_left_radius_px, top_right_radius_px, bottom_right_radius_px, bottom_left_radius_px };
  195. }
  196. void ViewportPaintable::resolve_paint_only_properties()
  197. {
  198. // Resolves layout-dependent properties not handled during layout and stores them in the paint tree.
  199. // Properties resolved include:
  200. // - Border radii
  201. // - Box shadows
  202. // - Text shadows
  203. // - Transforms
  204. // - Transform origins
  205. // - Outlines
  206. for_each_in_inclusive_subtree([&](Paintable& paintable) {
  207. auto& node = paintable.layout_node();
  208. auto const is_inline_paintable = paintable.is_inline_paintable();
  209. auto const is_paintable_box = paintable.is_paintable_box();
  210. auto const is_paintable_with_lines = paintable.is_paintable_with_lines();
  211. // Border radii
  212. if (is_inline_paintable) {
  213. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  214. auto& fragments = inline_paintable.fragments();
  215. auto const& top_left_border_radius = inline_paintable.computed_values().border_top_left_radius();
  216. auto const& top_right_border_radius = inline_paintable.computed_values().border_top_right_radius();
  217. auto const& bottom_right_border_radius = inline_paintable.computed_values().border_bottom_right_radius();
  218. auto const& bottom_left_border_radius = inline_paintable.computed_values().border_bottom_left_radius();
  219. auto containing_block_position_in_absolute_coordinates = inline_paintable.containing_block()->paintable_box()->absolute_position();
  220. for (size_t i = 0; i < fragments.size(); ++i) {
  221. auto is_first_fragment = i == 0;
  222. auto is_last_fragment = i == fragments.size() - 1;
  223. auto& fragment = fragments[i];
  224. CSSPixelRect absolute_fragment_rect {
  225. containing_block_position_in_absolute_coordinates.translated(fragment.offset()),
  226. fragment.size()
  227. };
  228. if (is_first_fragment) {
  229. auto extra_start_width = inline_paintable.box_model().padding.left;
  230. absolute_fragment_rect.translate_by(-extra_start_width, 0);
  231. absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_start_width);
  232. }
  233. if (is_last_fragment) {
  234. auto extra_end_width = inline_paintable.box_model().padding.right;
  235. absolute_fragment_rect.set_width(absolute_fragment_rect.width() + extra_end_width);
  236. }
  237. auto border_radii_data = normalize_border_radii_data(inline_paintable.layout_node(),
  238. absolute_fragment_rect, top_left_border_radius,
  239. top_right_border_radius,
  240. bottom_right_border_radius,
  241. bottom_left_border_radius);
  242. fragment.set_border_radii_data(border_radii_data);
  243. }
  244. }
  245. // Border radii
  246. if (is_paintable_box) {
  247. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  248. CSSPixelRect const border_rect { 0, 0, paintable_box.border_box_width(), paintable_box.border_box_height() };
  249. auto const& border_top_left_radius = node.computed_values().border_top_left_radius();
  250. auto const& border_top_right_radius = node.computed_values().border_top_right_radius();
  251. auto const& border_bottom_right_radius = node.computed_values().border_bottom_right_radius();
  252. auto const& border_bottom_left_radius = node.computed_values().border_bottom_left_radius();
  253. auto radii_data = normalize_border_radii_data(node, border_rect, border_top_left_radius,
  254. border_top_right_radius, border_bottom_right_radius,
  255. border_bottom_left_radius);
  256. paintable_box.set_border_radii_data(radii_data);
  257. }
  258. // Box shadows
  259. auto const& box_shadow_data = node.computed_values().box_shadow();
  260. if (!box_shadow_data.is_empty()) {
  261. Vector<Painting::ShadowData> resolved_box_shadow_data;
  262. resolved_box_shadow_data.ensure_capacity(box_shadow_data.size());
  263. for (auto const& layer : box_shadow_data) {
  264. resolved_box_shadow_data.empend(
  265. layer.color,
  266. layer.offset_x.to_px(node),
  267. layer.offset_y.to_px(node),
  268. layer.blur_radius.to_px(node),
  269. layer.spread_distance.to_px(node),
  270. layer.placement == CSS::ShadowPlacement::Outer ? Painting::ShadowPlacement::Outer
  271. : Painting::ShadowPlacement::Inner);
  272. }
  273. if (is<Painting::PaintableBox>(paintable)) {
  274. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  275. paintable_box.set_box_shadow_data(move(resolved_box_shadow_data));
  276. } else if (is<Painting::InlinePaintable>(paintable)) {
  277. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  278. inline_paintable.set_box_shadow_data(move(resolved_box_shadow_data));
  279. }
  280. }
  281. // Text shadows
  282. if (is_paintable_with_lines) {
  283. auto const& paintable_with_lines = static_cast<Painting::PaintableWithLines const&>(paintable);
  284. for (auto const& fragment : paintable_with_lines.fragments()) {
  285. auto const& text_shadow = fragment.m_layout_node->computed_values().text_shadow();
  286. if (!text_shadow.is_empty()) {
  287. Vector<Painting::ShadowData> resolved_shadow_data;
  288. resolved_shadow_data.ensure_capacity(text_shadow.size());
  289. for (auto const& layer : text_shadow) {
  290. resolved_shadow_data.empend(
  291. layer.color,
  292. layer.offset_x.to_px(paintable_with_lines.layout_node()),
  293. layer.offset_y.to_px(paintable_with_lines.layout_node()),
  294. layer.blur_radius.to_px(paintable_with_lines.layout_node()),
  295. layer.spread_distance.to_px(paintable_with_lines.layout_node()),
  296. Painting::ShadowPlacement::Outer);
  297. }
  298. const_cast<Painting::PaintableFragment&>(fragment).set_shadows(move(resolved_shadow_data));
  299. }
  300. }
  301. }
  302. // Transform and transform origin
  303. if (is_paintable_box) {
  304. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  305. auto const& transformations = paintable_box.computed_values().transformations();
  306. if (!transformations.is_empty()) {
  307. auto matrix = Gfx::FloatMatrix4x4::identity();
  308. for (auto const& transform : transformations)
  309. matrix = matrix * transform.to_matrix(paintable_box).release_value();
  310. paintable_box.set_transform(matrix);
  311. }
  312. auto const& transform_origin = paintable_box.computed_values().transform_origin();
  313. // https://www.w3.org/TR/css-transforms-1/#transform-box
  314. auto transform_box = paintable_box.computed_values().transform_box();
  315. // For SVG elements without associated CSS layout box, the used value for content-box is fill-box and for
  316. // border-box is stroke-box.
  317. // FIXME: This currently detects any SVG element except the <svg> one. Is that correct?
  318. // And is it correct to use `else` below?
  319. if (is<Painting::SVGPaintable>(paintable_box)) {
  320. switch (transform_box) {
  321. case CSS::TransformBox::ContentBox:
  322. transform_box = CSS::TransformBox::FillBox;
  323. break;
  324. case CSS::TransformBox::BorderBox:
  325. transform_box = CSS::TransformBox::StrokeBox;
  326. break;
  327. default:
  328. break;
  329. }
  330. }
  331. // For elements with associated CSS layout box, the used value for fill-box is content-box and for
  332. // stroke-box and view-box is border-box.
  333. else {
  334. switch (transform_box) {
  335. case CSS::TransformBox::FillBox:
  336. transform_box = CSS::TransformBox::ContentBox;
  337. break;
  338. case CSS::TransformBox::StrokeBox:
  339. case CSS::TransformBox::ViewBox:
  340. transform_box = CSS::TransformBox::BorderBox;
  341. break;
  342. default:
  343. break;
  344. }
  345. }
  346. CSSPixelRect reference_box = [&]() {
  347. switch (transform_box) {
  348. case CSS::TransformBox::ContentBox:
  349. // Uses the content box as reference box.
  350. // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
  351. return paintable_box.absolute_rect();
  352. case CSS::TransformBox::BorderBox:
  353. // Uses the border box as reference box.
  354. // FIXME: The reference box of a table is the border box of its table wrapper box, not its table box.
  355. return paintable_box.absolute_border_box_rect();
  356. case CSS::TransformBox::FillBox:
  357. // Uses the object bounding box as reference box.
  358. // FIXME: For now we're using the content rect as an approximation.
  359. return paintable_box.absolute_rect();
  360. case CSS::TransformBox::StrokeBox:
  361. // Uses the stroke bounding box as reference box.
  362. // FIXME: For now we're using the border rect as an approximation.
  363. return paintable_box.absolute_border_box_rect();
  364. case CSS::TransformBox::ViewBox:
  365. // Uses the nearest SVG viewport as reference box.
  366. // FIXME: If a viewBox attribute is specified for the SVG viewport creating element:
  367. // - The reference box is positioned at the origin of the coordinate system established by the viewBox attribute.
  368. // - The dimension of the reference box is set to the width and height values of the viewBox attribute.
  369. auto* svg_paintable = paintable_box.first_ancestor_of_type<Painting::SVGSVGPaintable>();
  370. if (!svg_paintable)
  371. return paintable_box.absolute_border_box_rect();
  372. return svg_paintable->absolute_rect();
  373. }
  374. VERIFY_NOT_REACHED();
  375. }();
  376. auto x = reference_box.left() + transform_origin.x.to_px(node, reference_box.width());
  377. auto y = reference_box.top() + transform_origin.y.to_px(node, reference_box.height());
  378. paintable_box.set_transform_origin({ x, y });
  379. paintable_box.set_transform_origin({ x, y });
  380. }
  381. // Outlines
  382. auto const& computed_values = node.computed_values();
  383. auto outline_width = computed_values.outline_width().to_px(node);
  384. auto outline_data = borders_data_for_outline(node, computed_values.outline_color(), computed_values.outline_style(), outline_width);
  385. auto outline_offset = computed_values.outline_offset().to_px(node);
  386. if (is_paintable_box) {
  387. auto& paintable_box = static_cast<Painting::PaintableBox&>(paintable);
  388. paintable_box.set_outline_data(outline_data);
  389. paintable_box.set_outline_offset(outline_offset);
  390. } else if (is_inline_paintable) {
  391. auto& inline_paintable = static_cast<Painting::InlinePaintable&>(paintable);
  392. inline_paintable.set_outline_data(outline_data);
  393. inline_paintable.set_outline_offset(outline_offset);
  394. }
  395. return TraversalDecision::Continue;
  396. });
  397. }
  398. }