1 | /* |
2 | * Copyright (C) 2018 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "FloatingState.h" |
28 | |
29 | #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
30 | |
31 | #include "FormattingContext.h" |
32 | #include "LayoutBox.h" |
33 | #include "LayoutContainer.h" |
34 | #include "LayoutState.h" |
35 | #include <wtf/IsoMallocInlines.h> |
36 | |
37 | namespace WebCore { |
38 | namespace Layout { |
39 | |
40 | WTF_MAKE_ISO_ALLOCATED_IMPL(FloatingState); |
41 | |
42 | FloatingState::FloatItem::FloatItem(const Box& layoutBox, const FloatingState& floatingState) |
43 | : m_layoutBox(makeWeakPtr(layoutBox)) |
44 | , m_absoluteDisplayBox(FormattingContext::mapBoxToAncestor(floatingState.layoutState(), layoutBox, downcast<Container>(floatingState.root()))) |
45 | { |
46 | } |
47 | |
48 | FloatingState::FloatingState(LayoutState& layoutState, const Box& formattingContextRoot) |
49 | : m_layoutState(layoutState) |
50 | , m_formattingContextRoot(makeWeakPtr(formattingContextRoot)) |
51 | { |
52 | } |
53 | |
54 | #ifndef NDEBUG |
55 | static bool belongsToThisFloatingContext(const Box& layoutBox, const Box& floatingStateRoot) |
56 | { |
57 | auto& formattingContextRoot = layoutBox.formattingContextRoot(); |
58 | if (&formattingContextRoot == &floatingStateRoot) |
59 | return true; |
60 | |
61 | // Maybe the layout box belongs to an inline formatting context that inherits the floating state from the parent (block) formatting context. |
62 | if (!formattingContextRoot.establishesInlineFormattingContext()) |
63 | return false; |
64 | |
65 | return &formattingContextRoot.formattingContextRoot() == &floatingStateRoot; |
66 | } |
67 | #endif |
68 | |
69 | void FloatingState::remove(const Box& layoutBox) |
70 | { |
71 | for (size_t index = 0; index < m_floats.size(); ++index) { |
72 | if (m_floats[index] == layoutBox) { |
73 | m_floats.remove(index); |
74 | return; |
75 | } |
76 | } |
77 | ASSERT_NOT_REACHED(); |
78 | } |
79 | |
80 | void FloatingState::append(const Box& layoutBox) |
81 | { |
82 | ASSERT(is<Container>(*m_formattingContextRoot)); |
83 | ASSERT(belongsToThisFloatingContext(layoutBox, *m_formattingContextRoot)); |
84 | ASSERT(is<Container>(*m_formattingContextRoot)); |
85 | |
86 | auto newFloatItem = FloatItem { layoutBox, *this }; |
87 | if (m_floats.isEmpty()) |
88 | return m_floats.append(newFloatItem); |
89 | |
90 | auto& displayBox = m_layoutState.displayBoxForLayoutBox(layoutBox); |
91 | auto isLeftPositioned = layoutBox.isLeftFloatingPositioned(); |
92 | // When adding a new float item to the list, we have to ensure that it is definitely the left(right)-most item. |
93 | // Normally it is, but negative horizontal margins can push the float box beyond another float box. |
94 | // Float items in m_floats list should stay in horizontal position order (left/right edge) on the same vertical position. |
95 | auto hasNegativeHorizontalMargin = (isLeftPositioned && displayBox.marginStart() < 0) || (!isLeftPositioned && displayBox.marginEnd() < 0); |
96 | if (!hasNegativeHorizontalMargin) |
97 | return m_floats.append(newFloatItem); |
98 | |
99 | for (int i = m_floats.size() - 1; i >= 0; --i) { |
100 | auto& floatItem = m_floats[i]; |
101 | if (isLeftPositioned != floatItem.isLeftPositioned()) |
102 | continue; |
103 | if (newFloatItem.rectWithMargin().top() < floatItem.rectWithMargin().bottom()) |
104 | continue; |
105 | if ((isLeftPositioned && newFloatItem.rectWithMargin().right() >= floatItem.rectWithMargin().right()) |
106 | || (!isLeftPositioned && newFloatItem.rectWithMargin().left() <= floatItem.rectWithMargin().left())) |
107 | return m_floats.insert(i + 1, newFloatItem); |
108 | } |
109 | return m_floats.insert(0, newFloatItem); |
110 | } |
111 | |
112 | FloatingState::Constraints FloatingState::constraints(PositionInContextRoot verticalPosition, const Box& formattingContextRoot) const |
113 | { |
114 | if (isEmpty()) |
115 | return { }; |
116 | |
117 | // 1. Convert vertical position if this floating context is inherited. |
118 | // 2. Find the inner left/right floats at verticalPosition. |
119 | // 3. Convert left/right positions back to formattingContextRoot's cooridnate system. |
120 | auto coordinateMappingIsRequired = &root() != &formattingContextRoot; |
121 | auto adjustedPosition = Point { 0, verticalPosition }; |
122 | |
123 | if (coordinateMappingIsRequired) |
124 | adjustedPosition = FormattingContext::mapCoordinateToAncestor(m_layoutState, adjustedPosition, downcast<Container>(formattingContextRoot), downcast<Container>(root())); |
125 | |
126 | Constraints constraints; |
127 | for (int index = m_floats.size() - 1; index >= 0; --index) { |
128 | auto& floatItem = m_floats[index]; |
129 | |
130 | if (constraints.left && floatItem.isLeftPositioned()) |
131 | continue; |
132 | |
133 | if (constraints.right && !floatItem.isLeftPositioned()) |
134 | continue; |
135 | |
136 | auto rect = floatItem.rectWithMargin(); |
137 | if (!(rect.top() <= adjustedPosition.y && adjustedPosition.y < rect.bottom())) |
138 | continue; |
139 | |
140 | if (floatItem.isLeftPositioned()) |
141 | constraints.left = PositionInContextRoot { rect.right() }; |
142 | else |
143 | constraints.right = PositionInContextRoot { rect.left() }; |
144 | |
145 | if (constraints.left && constraints.right) |
146 | break; |
147 | } |
148 | |
149 | if (coordinateMappingIsRequired) { |
150 | if (constraints.left) |
151 | constraints.left = PositionInContextRoot { *constraints.left - adjustedPosition.x }; |
152 | |
153 | if (constraints.right) |
154 | constraints.right = PositionInContextRoot { *constraints.right - adjustedPosition.x }; |
155 | } |
156 | |
157 | return constraints; |
158 | } |
159 | |
160 | Optional<PositionInContextRoot> FloatingState::bottom(const Box& formattingContextRoot, Clear type) const |
161 | { |
162 | if (m_floats.isEmpty()) |
163 | return { }; |
164 | |
165 | // TODO: Currently this is only called once for each formatting context root with floats per layout. |
166 | // Cache the value if we end up calling it more frequently (and update it at append/remove). |
167 | Optional<PositionInContextRoot> bottom; |
168 | for (auto& floatItem : m_floats) { |
169 | // Ignore floats from ancestor formatting contexts when the floating state is inherited. |
170 | if (!floatItem.isDescendantOfFormattingRoot(formattingContextRoot)) |
171 | continue; |
172 | |
173 | if ((type == Clear::Left && !floatItem.isLeftPositioned()) |
174 | || (type == Clear::Right && floatItem.isLeftPositioned())) |
175 | continue; |
176 | |
177 | auto floatsBottom = floatItem.rectWithMargin().bottom(); |
178 | if (bottom) { |
179 | bottom = std::max<PositionInContextRoot>(*bottom, { floatsBottom }); |
180 | continue; |
181 | } |
182 | bottom = PositionInContextRoot { floatsBottom }; |
183 | } |
184 | return bottom; |
185 | } |
186 | |
187 | Optional<PositionInContextRoot> FloatingState::top(const Box& formattingContextRoot) const |
188 | { |
189 | if (m_floats.isEmpty()) |
190 | return { }; |
191 | |
192 | Optional<PositionInContextRoot> top; |
193 | for (auto& floatItem : m_floats) { |
194 | // Ignore floats from ancestor formatting contexts when the floating state is inherited. |
195 | if (!floatItem.isDescendantOfFormattingRoot(formattingContextRoot)) |
196 | continue; |
197 | |
198 | auto floatTop = floatItem.rectWithMargin().top(); |
199 | if (top) { |
200 | top = std::max<PositionInContextRoot>(*top, { floatTop }); |
201 | continue; |
202 | } |
203 | top = PositionInContextRoot { floatTop }; |
204 | } |
205 | return top; |
206 | } |
207 | |
208 | } |
209 | } |
210 | #endif |
211 | |