[LayoutReloaded] Introduce FloatingState.
[WebKit-https.git] / Tools / LayoutReloaded / FormattingContext / FloatingContext.js
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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 // All geometry here is absolute to the formatting context's root.
27 class FloatingContext {
28     constructor(floatingState) {
29         this.m_floatingState = floatingState;
30     }
31
32     computePosition(layoutBox) {
33         if (layoutBox.isOutOfFlowPositioned())
34             return;
35         let displayBox = this._formattingContext().displayBox(layoutBox);
36         if (layoutBox.isFloatingPositioned()) {
37             displayBox.setTopLeft(this._positionForFloating(layoutBox));
38             this._floatingState().addFloating(layoutBox);
39             return;
40         }
41         if (Utils.hasClear(layoutBox))
42             return displayBox.setTopLeft(this._positionForClear(layoutBox));
43         // Intruding floats might force this box move.
44         displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
45     }
46
47     bottom() {
48         let leftBottom = this._bottom(this._leftFloatingStack());
49         let rightBottom = this._bottom(this._rightFloatingStack());
50         if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
51             return Number.NaN;
52         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
53             return Math.max(leftBottom, rightBottom);
54         if (!Number.isNaN(leftBottom))
55             return leftBottom;
56         return rightBottom;
57     }
58
59     _positionForFloating(floatingBox) {
60         let absoluteFloatingBox = this._formattingContext().absoluteMarginBox(floatingBox);
61         if (this._isEmpty())
62             return this._adjustedFloatingPosition(floatingBox, absoluteFloatingBox.top());
63         let verticalPosition = Math.max(absoluteFloatingBox.top(), this._lastFloating().top());
64         let spaceNeeded = absoluteFloatingBox.width();
65         while (true) {
66             let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
67             if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
68                 return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
69             verticalPosition = this._moveToNextVerticalPosition(floatingPair);
70         }
71         return Math.Nan;
72     }
73
74     _positionForClear(layoutBox) {
75         ASSERT(Utils.hasClear(layoutBox));
76         let displayBox = this._formattingContext().displayBox(layoutBox);
77         if (this._isEmpty())
78             return displayBox.topLeft();
79
80         let leftBottom = Number.NaN;
81         let rightBottom = Number.NaN;
82         if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
83             leftBottom = this._bottom(this._leftFloatingStack());
84         if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
85             rightBottom = this._bottom(this._rightFloatingStack());
86
87         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
88             return new LayoutPoint(Math.max(leftBottom, rightBottom), displayBox.left());
89         if (!Number.isNaN(leftBottom))
90             return new LayoutPoint(leftBottom, displayBox.left());
91         if (!Number.isNaN(rightBottom))
92             return new LayoutPoint(rightBottom, displayBox.left());
93         return displayBox.topLeft();
94     }
95
96     _computePositionToAvoidIntrudingFloats(layoutBox) {
97         if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
98             return this._formattingContext().displayBox(layoutBox).topLeft();
99         // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
100         // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
101         // margin box of any floats in the same block formatting context as the element itself.
102         // For some reason, we position this as if it was floating left.
103         return this._positionForFloating(layoutBox);
104     }
105
106     _findInnerMostLeftAndRight(verticalPosition) {
107         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatingStack());
108         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatingStack());
109         return { left: leftFloating, right: rightFloating };
110     }
111
112     _moveToNextVerticalPosition(floatingPair) {
113         if (!floatingPair.left && !floatingPair.right)
114             return Math.NaN;
115         let leftBottom = Number.POSITIVE_INFINITY;
116         let rightBottom = Number.POSITIVE_INFINITY;
117         if (floatingPair.left)
118             leftBottom = floatingPair.left.bottom();
119         if (floatingPair.right)
120             rightBottom = floatingPair.right.bottom();
121         return Math.min(leftBottom, rightBottom);
122     }
123
124     _availableSpace(containingBlock, floatingPair) {
125         let containingBlockContentBox = this._formattingContext().displayBox(containingBlock);
126         if (floatingPair.left && floatingPair.right)
127             return floatingPair.right.left() - floatingPair.left.right();
128         if (floatingPair.left)
129             return containingBlockContentBox.width() - (floatingPair.left.right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
130         if (floatingPair.right)
131             return floatingPair.right.left();
132         return containingBlockContentBox.width();
133     }
134
135     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
136         let index = floatingStack.length;
137         while (--index >= 0 && floatingStack[index].bottom() <= verticalPosition);
138         return index >= 0 ? floatingStack[index] : null;
139     }
140
141     _isEmpty() {
142         return !this._leftFloatingStack().length && !this._rightFloatingStack().length;
143     }
144
145     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
146         let containingBlock = floatingBox.containingBlock();
147         // Convert all coordinates relative to formatting context's root.
148         let left = this._formattingContext().absoluteContentBox(containingBlock).left();
149         let right = this._formattingContext().absoluteContentBox(containingBlock).right();
150         if (leftRightFloatings) {
151             if (leftRightFloatings.left) {
152                 let floatingBoxRight = leftRightFloatings.left.right();
153                 if (floatingBoxRight > left)
154                     left = floatingBoxRight;
155             }
156
157             if (leftRightFloatings.right) {
158                 let floatingBoxLeft = leftRightFloatings.right.left();
159                 if (floatingBoxLeft < right)
160                     right = floatingBoxLeft;
161             }
162         }
163         left += this._formattingContext().marginLeft(floatingBox);
164         right -= this._formattingContext().marginRight(floatingBox);
165         verticalPosition += this._formattingContext().marginTop(floatingBox);
166         // No convert them back relative to the floatingBox's containing block.
167         let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
168         let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
169         left -= containingBlockLeft;
170         right -= containingBlockLeft;
171         verticalPosition -= containingBlockTop;
172
173         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
174             return new LayoutPoint(verticalPosition, left);
175         return new LayoutPoint(verticalPosition, right - this._formattingContext().displayBox(floatingBox).rect().width());
176     }
177
178     _bottom(floatingStack) {
179         if (!floatingStack || !floatingStack.length)
180             return Number.NaN;
181         let max = Number.NEGATIVE_INFINITY;
182         for (let i = 0; i < floatingStack.length; ++i)
183             max = Math.max(floatingStack[i].bottom(), max);
184         return max;
185     }
186
187     _floatingState() {
188         return this.m_floatingState;
189     }
190
191     _formattingContext() {
192         return this._floatingState().formattingContext();
193     }
194
195     _lastFloating() {
196         return this._floatingState().lastFloating();
197     }
198
199     _leftFloatingStack() {
200         return this._floatingState().leftFloatingStack();
201     }
202
203     _rightFloatingStack() {
204         return this._floatingState().rightFloatingStack();
205     }
206 }