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
37namespace WebCore {
38namespace Layout {
39
40WTF_MAKE_ISO_ALLOCATED_IMPL(FloatingState);
41
42FloatingState::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
48FloatingState::FloatingState(LayoutState& layoutState, const Box& formattingContextRoot)
49 : m_layoutState(layoutState)
50 , m_formattingContextRoot(makeWeakPtr(formattingContextRoot))
51{
52}
53
54#ifndef NDEBUG
55static 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
69void 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
80void 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
112FloatingState::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
160Optional<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
187Optional<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