FormattingContext.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  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. float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& box)
  112. {
  113. // 10.3.4 Block-level, replaced elements in normal flow...
  114. // 10.3.2 Inline, replaced elements
  115. auto zero_value = CSS::Length::make_px(0);
  116. auto& containing_block = *box.containing_block();
  117. auto margin_left = box.style().margin().left.resolved_or_zero(box, containing_block.width());
  118. auto margin_right = box.style().margin().right.resolved_or_zero(box, containing_block.width());
  119. // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
  120. if (margin_left.is_auto())
  121. margin_left = zero_value;
  122. if (margin_right.is_auto())
  123. margin_right = zero_value;
  124. auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width());
  125. auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height());
  126. // FIXME: Actually compute 'width'
  127. auto computed_width = specified_width;
  128. float used_width = specified_width.to_px(box);
  129. // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width,
  130. // then that intrinsic width is the used value of 'width'.
  131. if (specified_height.is_auto() && specified_width.is_auto() && box.has_intrinsic_width()) {
  132. used_width = box.intrinsic_width();
  133. }
  134. // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width,
  135. // but does have an intrinsic height and intrinsic ratio;
  136. // or if 'width' has a computed value of 'auto',
  137. // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is:
  138. //
  139. // (used height) * (intrinsic ratio)
  140. else if ((specified_height.is_auto() && specified_width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_ratio()) || (computed_width.is_auto() && box.has_intrinsic_ratio())) {
  141. used_width = compute_height_for_replaced_element(box) * box.intrinsic_ratio();
  142. }
  143. else if (computed_width.is_auto() && box.has_intrinsic_width()) {
  144. used_width = box.intrinsic_width();
  145. }
  146. else if (computed_width.is_auto()) {
  147. used_width = 300;
  148. }
  149. return used_width;
  150. }
  151. float FormattingContext::compute_height_for_replaced_element(const ReplacedBox& box)
  152. {
  153. // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow,
  154. // 'inline-block' replaced elements in normal flow and floating replaced elements
  155. auto& containing_block = *box.containing_block();
  156. auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width());
  157. auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height());
  158. float used_height = specified_height.to_px(box);
  159. // If 'height' and 'width' both have computed values of 'auto' and the element also has
  160. // an intrinsic height, then that intrinsic height is the used value of 'height'.
  161. if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_height())
  162. used_height = box.intrinsic_height();
  163. else if (specified_height.is_auto() && box.has_intrinsic_ratio())
  164. used_height = compute_width_for_replaced_element(box) / box.intrinsic_ratio();
  165. else if (specified_height.is_auto() && box.has_intrinsic_height())
  166. used_height = box.intrinsic_height();
  167. else if (specified_height.is_auto())
  168. used_height = 150;
  169. return used_height;
  170. }
  171. }