FormattingContext.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*
  2. * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <LibWeb/Dump.h>
  27. #include <LibWeb/Layout/BlockBox.h>
  28. #include <LibWeb/Layout/BlockFormattingContext.h>
  29. #include <LibWeb/Layout/Box.h>
  30. #include <LibWeb/Layout/FormattingContext.h>
  31. #include <LibWeb/Layout/InlineFormattingContext.h>
  32. #include <LibWeb/Layout/ReplacedBox.h>
  33. #include <LibWeb/Layout/TableFormattingContext.h>
  34. namespace Web::Layout {
  35. FormattingContext::FormattingContext(Box& context_box, FormattingContext* parent)
  36. : m_parent(parent)
  37. , m_context_box(&context_box)
  38. {
  39. }
  40. FormattingContext::~FormattingContext()
  41. {
  42. }
  43. bool FormattingContext::creates_block_formatting_context(const Box& box)
  44. {
  45. if (box.is_root_element())
  46. return true;
  47. if (box.is_floating())
  48. return true;
  49. if (box.is_absolutely_positioned())
  50. return true;
  51. if (box.is_inline_block())
  52. return true;
  53. if (box.is_table_cell())
  54. return true;
  55. // FIXME: table-caption
  56. // FIXME: anonymous table cells
  57. // FIXME: Block elements where overflow has a value other than visible and clip.
  58. // FIXME: display: flow-root
  59. // FIXME: Elements with contain: layout, content, or paint.
  60. // FIXME: flex
  61. // FIXME: grid
  62. // FIXME: multicol
  63. // FIXME: column-span: all
  64. return false;
  65. }
  66. void FormattingContext::layout_inside(Box& box, LayoutMode layout_mode)
  67. {
  68. if (creates_block_formatting_context(box)) {
  69. BlockFormattingContext context(box, this);
  70. context.run(box, layout_mode);
  71. return;
  72. }
  73. if (box.is_table()) {
  74. TableFormattingContext context(box, this);
  75. context.run(box, layout_mode);
  76. } else if (box.children_are_inline()) {
  77. InlineFormattingContext context(box, this);
  78. context.run(box, layout_mode);
  79. } else {
  80. // FIXME: This needs refactoring!
  81. ASSERT(is_block_formatting_context());
  82. run(box, layout_mode);
  83. }
  84. }
  85. static float greatest_child_width(const Box& box)
  86. {
  87. float max_width = 0;
  88. if (box.children_are_inline()) {
  89. for (auto& child : box.line_boxes()) {
  90. max_width = max(max_width, child.width());
  91. }
  92. } else {
  93. box.for_each_child_of_type<Box>([&](auto& child) {
  94. max_width = max(max_width, child.border_box_width());
  95. });
  96. }
  97. return max_width;
  98. }
  99. FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_widths(Box& box)
  100. {
  101. // Calculate the preferred width by formatting the content without breaking lines
  102. // other than where explicit line breaks occur.
  103. layout_inside(box, LayoutMode::OnlyRequiredLineBreaks);
  104. float preferred_width = greatest_child_width(box);
  105. // Also calculate the preferred minimum width, e.g., by trying all possible line breaks.
  106. // CSS 2.2 does not define the exact algorithm.
  107. layout_inside(box, LayoutMode::AllPossibleLineBreaks);
  108. float preferred_minimum_width = greatest_child_width(box);
  109. return { preferred_width, preferred_minimum_width };
  110. }
  111. static Gfx::FloatSize solve_replaced_size_constraint(float w, float h, const ReplacedBox& box)
  112. {
  113. // 10.4 Minimum and maximum widths: 'min-width' and 'max-width'
  114. auto& containing_block = *box.containing_block();
  115. auto specified_min_width = box.style().min_width().resolved_or_zero(box, containing_block.width()).to_px(box);
  116. auto specified_max_width = box.style().max_width().resolved(CSS::Length::make_px(w), box, containing_block.width()).to_px(box);
  117. auto specified_min_height = box.style().min_height().resolved_or_auto(box, containing_block.height()).to_px(box);
  118. auto specified_max_height = box.style().max_height().resolved(CSS::Length::make_px(h), box, containing_block.height()).to_px(box);
  119. auto min_width = min(specified_min_width, specified_max_width);
  120. auto max_width = max(specified_min_width, specified_max_width);
  121. auto min_height = min(specified_min_height, specified_max_height);
  122. auto max_height = max(specified_min_height, specified_max_height);
  123. if (w > max_width)
  124. return { w, max(max_width * h / w, min_height) };
  125. if (w < min_width)
  126. return { max_width, min(min_width * h / w, max_height) };
  127. if (h > max_height)
  128. return { max(max_height * w / h, min_width), max_height };
  129. if (h < min_height)
  130. return { min(min_height * w / h, max_width), min_height };
  131. if ((w > max_width && h > max_height) && (max_width / w < max_height / h))
  132. return { max_width, max(min_height, max_width * h / w) };
  133. if ((w > max_width && h > max_height) && (max_width / w > max_height / h))
  134. return { max(min_width, max_height * w / h), max_height };
  135. if ((w < min_width && h < min_height) && (min_width / w < min_height / h))
  136. return { min(max_width, min_height * w / h), min_height };
  137. if ((w < min_width && h < min_height) && (min_width / w > min_height / h))
  138. return { min_width, min(max_height, min_width * h / w) };
  139. if (w < min_width && h > max_height)
  140. return { min_width, max_height };
  141. if (w > max_width && h < min_height)
  142. return { max_width, min_height };
  143. return { w, h };
  144. }
  145. float FormattingContext::tentative_width_for_replaced_element(const ReplacedBox& box, const CSS::Length& width)
  146. {
  147. auto& containing_block = *box.containing_block();
  148. auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height());
  149. float used_width = width.to_px(box);
  150. // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width,
  151. // then that intrinsic width is the used value of 'width'.
  152. if (specified_height.is_auto() && width.is_auto() && box.has_intrinsic_width()) {
  153. used_width = box.intrinsic_width();
  154. }
  155. // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width,
  156. // but does have an intrinsic height and intrinsic ratio;
  157. // or if 'width' has a computed value of 'auto',
  158. // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is:
  159. //
  160. // (used height) * (intrinsic ratio)
  161. else if ((specified_height.is_auto() && width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_ratio()) || (width.is_auto() && box.has_intrinsic_ratio())) {
  162. used_width = compute_height_for_replaced_element(box) * box.intrinsic_ratio();
  163. }
  164. else if (width.is_auto() && box.has_intrinsic_width()) {
  165. used_width = box.intrinsic_width();
  166. }
  167. else if (width.is_auto()) {
  168. used_width = 300;
  169. }
  170. return used_width;
  171. }
  172. float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& box)
  173. {
  174. // 10.3.4 Block-level, replaced elements in normal flow...
  175. // 10.3.2 Inline, replaced elements
  176. auto zero_value = CSS::Length::make_px(0);
  177. auto& containing_block = *box.containing_block();
  178. auto margin_left = box.style().margin().left.resolved_or_zero(box, containing_block.width());
  179. auto margin_right = box.style().margin().right.resolved_or_zero(box, containing_block.width());
  180. // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
  181. if (margin_left.is_auto())
  182. margin_left = zero_value;
  183. if (margin_right.is_auto())
  184. margin_right = zero_value;
  185. auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width());
  186. // 1. The tentative used width is calculated (without 'min-width' and 'max-width')
  187. auto used_width = tentative_width_for_replaced_element(box, specified_width);
  188. // 2. The tentative used width is greater than 'max-width', the rules above are applied again,
  189. // but this time using the computed value of 'max-width' as the computed value for 'width'.
  190. auto specified_max_width = box.style().max_width().resolved_or_auto(box, containing_block.width());
  191. if (!specified_max_width.is_auto()) {
  192. if (used_width > specified_max_width.to_px(box)) {
  193. used_width = tentative_width_for_replaced_element(box, specified_max_width);
  194. }
  195. }
  196. // 3. If the resulting width is smaller than 'min-width', the rules above are applied again,
  197. // but this time using the value of 'min-width' as the computed value for 'width'.
  198. auto specified_min_width = box.style().min_width().resolved_or_auto(box, containing_block.width());
  199. if (!specified_min_width.is_auto()) {
  200. if (used_width < specified_min_width.to_px(box)) {
  201. used_width = tentative_width_for_replaced_element(box, specified_min_width);
  202. }
  203. }
  204. return used_width;
  205. }
  206. float FormattingContext::tentative_height_for_replaced_element(const ReplacedBox& box, const CSS::Length& height)
  207. {
  208. auto& containing_block = *box.containing_block();
  209. auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width());
  210. float used_height = height.to_px(box);
  211. // If 'height' and 'width' both have computed values of 'auto' and the element also has
  212. // an intrinsic height, then that intrinsic height is the used value of 'height'.
  213. if (specified_width.is_auto() && height.is_auto() && box.has_intrinsic_height())
  214. used_height = box.intrinsic_height();
  215. else if (height.is_auto() && box.has_intrinsic_ratio())
  216. used_height = compute_width_for_replaced_element(box) / box.intrinsic_ratio();
  217. else if (height.is_auto() && box.has_intrinsic_height())
  218. used_height = box.intrinsic_height();
  219. else if (height.is_auto())
  220. used_height = 150;
  221. return used_height;
  222. }
  223. float FormattingContext::compute_height_for_replaced_element(const ReplacedBox& box)
  224. {
  225. // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
  226. // 'inline-block' replaced elements in normal flow and floating replaced elements
  227. auto& containing_block = *box.containing_block();
  228. auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width());
  229. auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height());
  230. float used_height = tentative_height_for_replaced_element(box, specified_height);
  231. if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_ratio()) {
  232. float w = tentative_width_for_replaced_element(box, specified_width);
  233. float h = used_height;
  234. used_height = solve_replaced_size_constraint(w, h, box).height();
  235. }
  236. return used_height;
  237. }
  238. }