[LayoutReloaded] Remove FormattingContext::absoluteMarginBox
[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, parentFormattingContext) {
29         this.m_floatingState = floatingState;
30         this.m_parentFormattingContext = parentFormattingContext;
31     }
32
33     computePosition(layoutBox) {
34         if (layoutBox.isOutOfFlowPositioned())
35             return;
36         let displayBox = this._formattingState().displayBox(layoutBox);
37         if (layoutBox.isFloatingPositioned()) {
38             displayBox.setTopLeft(this._positionForFloating(layoutBox));
39             this._addFloatingBox(layoutBox);
40             return;
41         }
42         if (Utils.hasClear(layoutBox))
43             return displayBox.setTopLeft(this._positionForClear(layoutBox));
44         // Intruding floats might force this box move.
45         displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
46     }
47
48     bottom() {
49         let leftBottom = this._bottom(this._leftFloatingStack());
50         let rightBottom = this._bottom(this._rightFloatingStack());
51         if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
52             return Number.NaN;
53         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
54             return Math.max(leftBottom, rightBottom);
55         if (!Number.isNaN(leftBottom))
56             return leftBottom;
57         return rightBottom;
58     }
59
60     _positionForFloating(floatingBox) {
61         let absoluteFloatingBox = this._mapMarginBoxToFormattingRoot(floatingBox);
62         if (this._isEmpty())
63             return this._adjustedFloatingPosition(floatingBox, absoluteFloatingBox.top());
64         let verticalPosition = Math.max(absoluteFloatingBox.top(), this._lastFloating().top());
65         let spaceNeeded = absoluteFloatingBox.width();
66         while (true) {
67             let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
68             if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
69                 return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
70             verticalPosition = this._moveToNextVerticalPosition(floatingPair);
71         }
72         return Math.Nan;
73     }
74
75     _positionForClear(layoutBox) {
76         ASSERT(Utils.hasClear(layoutBox));
77         let displayBox = this._formattingState().displayBox(layoutBox);
78         if (this._isEmpty())
79             return displayBox.topLeft();
80
81         let leftBottom = Number.NaN;
82         let rightBottom = Number.NaN;
83         if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
84             leftBottom = this._bottom(this._leftFloatingStack());
85         if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
86             rightBottom = this._bottom(this._rightFloatingStack());
87
88         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
89             return new LayoutPoint(Math.max(leftBottom, rightBottom), displayBox.left());
90         if (!Number.isNaN(leftBottom))
91             return new LayoutPoint(leftBottom, displayBox.left());
92         if (!Number.isNaN(rightBottom))
93             return new LayoutPoint(rightBottom, displayBox.left());
94         return displayBox.topLeft();
95     }
96
97     _computePositionToAvoidIntrudingFloats(layoutBox) {
98         if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
99             return this._formattingState().displayBox(layoutBox).topLeft();
100         // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
101         // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
102         // margin box of any floats in the same block formatting context as the element itself.
103         // For some reason, we position this as if it was floating left.
104         return this._positionForFloating(layoutBox);
105     }
106
107     _findInnerMostLeftAndRight(verticalPosition) {
108         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatingStack());
109         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatingStack());
110         return { left: leftFloating, right: rightFloating };
111     }
112
113     _moveToNextVerticalPosition(floatingPair) {
114         if (!floatingPair.left && !floatingPair.right)
115             return Math.NaN;
116         let leftBottom = Number.POSITIVE_INFINITY;
117         let rightBottom = Number.POSITIVE_INFINITY;
118         if (floatingPair.left)
119             leftBottom = floatingPair.left.bottom();
120         if (floatingPair.right)
121             rightBottom = floatingPair.right.bottom();
122         return Math.min(leftBottom, rightBottom);
123     }
124
125     _availableSpace(containingBlock, floatingPair) {
126         let containingBlockContentBox = this._formattingContext().displayBox(containingBlock);
127         if (floatingPair.left && floatingPair.right)
128             return floatingPair.right.left() - floatingPair.left.right();
129         if (floatingPair.left)
130             return containingBlockContentBox.width() - (floatingPair.left.right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
131         if (floatingPair.right)
132             return floatingPair.right.left();
133         return containingBlockContentBox.width();
134     }
135
136     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
137         let index = floatingStack.length;
138         while (--index >= 0 && floatingStack[index].bottom() <= verticalPosition);
139         return index >= 0 ? floatingStack[index] : null;
140     }
141
142     _isEmpty() {
143         return !this._leftFloatingStack().length && !this._rightFloatingStack().length;
144     }
145
146     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
147         let containingBlock = floatingBox.containingBlock();
148         // Convert all coordinates relative to formatting context's root.
149         let left = this._formattingContext().absoluteContentBox(containingBlock).left();
150         let right = this._formattingContext().absoluteContentBox(containingBlock).right();
151         if (leftRightFloatings) {
152             if (leftRightFloatings.left) {
153                 let floatingBoxRight = leftRightFloatings.left.right();
154                 if (floatingBoxRight > left)
155                     left = floatingBoxRight;
156             }
157
158             if (leftRightFloatings.right) {
159                 let floatingBoxLeft = leftRightFloatings.right.left();
160                 if (floatingBoxLeft < right)
161                     right = floatingBoxLeft;
162             }
163         }
164         left += this._formattingContext().marginLeft(floatingBox);
165         right -= this._formattingContext().marginRight(floatingBox);
166         verticalPosition += this._formattingContext().marginTop(floatingBox);
167         // No convert them back relative to the floatingBox's containing block.
168         let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
169         let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
170         left -= containingBlockLeft;
171         right -= containingBlockLeft;
172         verticalPosition -= containingBlockTop;
173
174         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
175             return new LayoutPoint(verticalPosition, left);
176         return new LayoutPoint(verticalPosition, right - this._formattingContext().displayBox(floatingBox).rect().width());
177     }
178
179     _bottom(floatingStack) {
180         if (!floatingStack || !floatingStack.length)
181             return Number.NaN;
182         let max = Number.NEGATIVE_INFINITY;
183         for (let i = 0; i < floatingStack.length; ++i)
184             max = Math.max(floatingStack[i].bottom(), max);
185         return max;
186     }
187
188     _addFloatingBox(layoutBox) {
189         // Convert floating box to absolute.
190         let clonedDisplayBox = this._formattingContext().displayBox(layoutBox).clone();
191         clonedDisplayBox.setRect(this._mapMarginBoxToFormattingRoot(layoutBox));
192         this._floatingState().addFloating(layoutBox, clonedDisplayBox);
193     }
194
195     _mapMarginBoxToFormattingRoot(layoutBox) {
196         let displayBox = this._formattingState().displayBox(layoutBox);
197         let rootDisplayBox = this._formattingState().displayBox(this._formattingRoot());
198         return Utils.marginBox(displayBox, rootDisplayBox);
199     }
200
201     _floatingState() {
202         return this.m_floatingState;
203     }
204
205     _formattingContext() {
206         return this.m_parentFormattingContext;
207     }
208
209     _formattingRoot() {
210         return this._formattingState().formattingRoot();
211     }
212
213     _formattingState() {
214         return this._floatingState().formattingState();
215     }
216
217     _lastFloating() {
218         return this._floatingState().lastFloating();
219     }
220
221     _leftFloatingStack() {
222         return this._floatingState().leftFloatingStack();
223     }
224
225     _rightFloatingStack() {
226         return this._floatingState().rightFloatingStack();
227     }
228 }