[LayoutReloaded] Remove border/padding/contentBox() functions from Layout.Box
[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 class FloatingContext {
27     constructor(formattingContext) {
28         this.m_leftFloatingBoxStack = new Array();
29         this.m_rightFloatingBoxStack = new Array();
30         this.m_lastFloating = null;
31         this.m_formattingContext = formattingContext;
32     }
33
34     computePosition(layoutBox) {
35         if (layoutBox.isOutOfFlowPositioned())
36             return;
37         let displayBox = this._formattingContext().toDisplayBox(layoutBox);
38         if (layoutBox.isFloatingPositioned()) {
39             let position = this._positionForFloating(layoutBox);
40             this._addFloating(layoutBox);
41             return displayBox.setTopLeft(position);
42         }
43         if (Utils.hasClear(layoutBox))
44             return displayBox.setTopLeft(this._positionForClear(layoutBox));
45         // Intruding floats might force this box move.
46         displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
47     }
48
49     bottom() {
50         let leftBottom = this._bottom(this.m_leftFloatingBoxStack);
51         let rightBottom = this._bottom(this.m_rightFloatingBoxStack);
52         if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
53             return Number.NaN;
54         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
55             return Math.max(leftBottom, rightBottom);
56         if (!Number.isNaN(leftBottom))
57             return leftBottom;
58         return rightBottom;
59     }
60
61     _positionForFloating(floatingBox) {
62         if (this._isEmpty())
63             return this._adjustedFloatingPosition(floatingBox, this._formattingContext().absoluteMarginBox(floatingBox).top());
64         let verticalPosition = Math.max(this._formattingContext().absoluteMarginBox(floatingBox).top(), this._formattingContext().absoluteMarginBox(this.m_lastFloating).top());
65         let spaceNeeded = this._formattingContext().absoluteMarginBox(floatingBox).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._formattingContext().toDisplayBox(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.m_leftFloatingBoxStack);
85         if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
86             rightBottom = this._bottom(this.m_rightFloatingBoxStack);
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._formattingContext().toDisplayBox(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     _addFloating(floatingBox) {
108         this.m_lastFloating = floatingBox;
109         if (Utils.isFloatingLeft(floatingBox)) {
110             this.m_leftFloatingBoxStack.push(floatingBox);
111             return;
112         }
113         this.m_rightFloatingBoxStack.push(floatingBox);
114     }
115
116     _findInnerMostLeftAndRight(verticalPosition) {
117         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_leftFloatingBoxStack);
118         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_rightFloatingBoxStack);
119         return { left: leftFloating, right: rightFloating };
120     }
121
122     _moveToNextVerticalPosition(floatingPair) {
123         if (!floatingPair.left && !floatingPair.right)
124             return Math.NaN;
125         let leftBottom = Number.POSITIVE_INFINITY;
126         let rightBottom = Number.POSITIVE_INFINITY;
127         if (floatingPair.left)
128             leftBottom = this._formattingContext().absoluteMarginBox(floatingPair.left).bottom();
129         if (floatingPair.right)
130             rightBottom = this._formattingContext().absoluteMarginBox(floatingPair.right).bottom();
131         return Math.min(leftBottom, rightBottom);
132     }
133
134     _availableSpace(containingBlock, floatingPair) {
135         let containingBlockContentBox = this._formattingContext().toDisplayBox(containingBlock);
136         if (floatingPair.left && floatingPair.right)
137             return this._formattingContext().absoluteMarginBox(floatingPair.right).left() - this._formattingContext().absoluteMarginBox(floatingPair.left).right();
138         if (floatingPair.left)
139             return containingBlockContentBox.width() - (this._formattingContext().absoluteMarginBox(floatingPair.left).right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
140         if (floatingPair.right)
141             return this._formattingContext().absoluteMarginBox(floatingPair.right).left();
142         return containingBlockContentBox.width();
143     }
144
145     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
146         let index = floatingStack.length;
147         while (--index >= 0 && this._formattingContext().absoluteMarginBox(floatingStack[index]).bottom() <= verticalPosition);
148         return index >= 0 ? floatingStack[index] : null;
149     }
150
151     _isEmpty() {
152         return !this.m_leftFloatingBoxStack.length && !this.m_rightFloatingBoxStack.length;
153     }
154
155     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
156         let containingBlock = floatingBox.containingBlock();
157         // Convert all coordinates relative to formatting context's root.
158         let left = this._formattingContext().absoluteContentBox(containingBlock).left();
159         let right = this._formattingContext().absoluteContentBox(containingBlock).right();
160         if (leftRightFloatings) {
161             if (leftRightFloatings.left) {
162                 let floatingBoxRight = this._formattingContext().absoluteMarginBox(leftRightFloatings.left).right();
163                 if (floatingBoxRight > left)
164                     left = floatingBoxRight;
165             }
166
167             if (leftRightFloatings.right) {
168                 let floatingBoxLeft = this._formattingContext().absoluteMarginBox(leftRightFloatings.right).left();
169                 if (floatingBoxLeft < right)
170                     right = floatingBoxLeft;
171             }
172         }
173         left += this._formattingContext().marginLeft(floatingBox);
174         right -= this._formattingContext().marginRight(floatingBox);
175         verticalPosition += this._formattingContext().marginTop(floatingBox);
176         // No convert them back relative to the floatingBox's containing block.
177         let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
178         let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
179         left -= containingBlockLeft;
180         right -= containingBlockLeft;
181         verticalPosition -= containingBlockTop;
182
183         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
184             return new LayoutPoint(verticalPosition, left);
185         return new LayoutPoint(verticalPosition, right - this._formattingContext().toDisplayBox(floatingBox).rect().width());
186     }
187
188     _bottom(floatingStack) {
189         if (!floatingStack || !floatingStack.length)
190             return Number.NaN;
191         let max = Number.NEGATIVE_INFINITY;
192         for (let i = 0; i < floatingStack.length; ++i)
193             max = Math.max(this._formattingContext().absoluteMarginBox(floatingStack[i]).bottom(), max);
194         return max;
195     }
196
197     _formattingContext() {
198         return this.m_formattingContext;
199     }
200 }