[LayoutReloaded] Add Line class for InlineFormattingContext -and move files around.
[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(box) {
35         if (box.isOutOfFlowPositioned())
36             return box.topLeft();
37         if (box.isFloatingPositioned()) {
38             let position = this._positionForFloating(box);
39             this._addFloating(box);
40             return position;
41         }
42         if (Utils.hasClear(box))
43             return this._positionForClear(box);
44         // Intruding floats might force this box move.
45         return this._computePositionToAvoidIntrudingFloats(box);
46     }
47
48     bottom() {
49         let leftBottom = this._bottom(this.m_leftFloatingBoxStack);
50         let rightBottom = this._bottom(this.m_rightFloatingBoxStack);
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         if (this._isEmpty())
62             return this._adjustedFloatingPosition(floatingBox, this._formattingContext().absoluteMarginBox(floatingBox).top());
63         let verticalPosition = Math.max(this._formattingContext().absoluteMarginBox(floatingBox).top(), this._formattingContext().absoluteMarginBox(this.m_lastFloating).top());
64         let spaceNeeded = this._formattingContext().absoluteMarginBox(floatingBox).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(box) {
75         ASSERT(Utils.hasClear(box));
76         if (this._isEmpty())
77             return box.topLeft();
78
79         let leftBottom = Number.NaN;
80         let rightBottom = Number.NaN;
81         if (Utils.hasClearLeft(box) || Utils.hasClearBoth(box))
82             leftBottom = this._bottom(this.m_leftFloatingBoxStack);
83         if (Utils.hasClearRight(box) || Utils.hasClearBoth(box))
84             rightBottom = this._bottom(this.m_rightFloatingBoxStack);
85
86         if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
87             return new LayoutPoint(Math.max(leftBottom, rightBottom), box.rect().left());
88         if (!Number.isNaN(leftBottom))
89             return new LayoutPoint(leftBottom, box.rect().left());
90         if (!Number.isNaN(rightBottom))
91             return new LayoutPoint(rightBottom, box.rect().left());
92         return box.topLeft();
93     }
94
95     _computePositionToAvoidIntrudingFloats(box) {
96         if (!box.establishesBlockFormattingContext() || this._isEmpty())
97             return box.topLeft();
98         // The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
99         // a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
100         // margin box of any floats in the same block formatting context as the element itself.
101         // For some reason, we position this as if it was floating left.
102         return this._positionForFloating(box);
103     }
104
105     _addFloating(floatingBox) {
106         this.m_lastFloating = floatingBox;
107         if (Utils.isFloatingLeft(floatingBox)) {
108             this.m_leftFloatingBoxStack.push(floatingBox);
109             return;
110         }
111         this.m_rightFloatingBoxStack.push(floatingBox);
112     }
113
114     _findInnerMostLeftAndRight(verticalPosition) {
115         let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_leftFloatingBoxStack);
116         let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this.m_rightFloatingBoxStack);
117         return { left: leftFloating, right: rightFloating };
118     }
119
120     _moveToNextVerticalPosition(floatingPair) {
121         if (!floatingPair.left && !floatingPair.right)
122             return Math.NaN;
123         let leftBottom = Number.POSITIVE_INFINITY;
124         let rightBottom = Number.POSITIVE_INFINITY;
125         if (floatingPair.left)
126             leftBottom = this._formattingContext().absoluteMarginBox(floatingPair.left).bottom();
127         if (floatingPair.right)
128             rightBottom = this._formattingContext().absoluteMarginBox(floatingPair.right).bottom();
129         return Math.min(leftBottom, rightBottom);
130     }
131
132     _availableSpace(containingBlock, floatingPair) {
133         if (floatingPair.left && floatingPair.right)
134             return this._formattingContext().absoluteMarginBox(floatingPair.right).left() - this._formattingContext().absoluteMarginBox(floatingPair.left).right();
135         if (floatingPair.left)
136             return containingBlock.contentBox().width() - (this._formattingContext().absoluteMarginBox(floatingPair.left).right() - this._formattingContext().absoluteBorderBox(containingBlock).left());
137         if (floatingPair.right)
138             return this._formattingContext().absoluteMarginBox(floatingPair.right).left();
139         return containingBlock.contentBox().width();
140     }
141
142     _findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
143         let index = floatingStack.length;
144         while (--index >= 0 && this._formattingContext().absoluteMarginBox(floatingStack[index]).bottom() <= verticalPosition);
145         return index >= 0 ? floatingStack[index] : null;
146     }
147
148     _isEmpty() {
149         return !this.m_leftFloatingBoxStack.length && !this.m_rightFloatingBoxStack.length;
150     }
151
152     _adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
153         let containingBlock = floatingBox.containingBlock();
154         // Convert all coordinates relative to formatting context's root.
155         let left = this._formattingContext().absoluteContentBox(containingBlock).left();
156         let right = this._formattingContext().absoluteContentBox(containingBlock).right();
157         if (leftRightFloatings) {
158             if (leftRightFloatings.left) {
159                 let floatingBoxRight = this._formattingContext().absoluteMarginBox(leftRightFloatings.left).right();
160                 if (floatingBoxRight > left)
161                     left = floatingBoxRight;
162             }
163
164             if (leftRightFloatings.right) {
165                 let floatingBoxLeft = this._formattingContext().absoluteMarginBox(leftRightFloatings.right).left();
166                 if (floatingBoxLeft < right)
167                     right = floatingBoxLeft;
168             }
169         }
170         left += this._formattingContext().marginLeft(floatingBox);
171         right -= this._formattingContext().marginRight(floatingBox);
172         verticalPosition += this._formattingContext().marginTop(floatingBox);
173         // No convert them back relative to the floatingBox's containing block.
174         let containingBlockLeft = this._formattingContext().absoluteBorderBox(containingBlock).left();
175         let containingBlockTop = this._formattingContext().absoluteBorderBox(containingBlock).top();
176         left -= containingBlockLeft;
177         right -= containingBlockLeft;
178         verticalPosition -= containingBlockTop;
179
180         if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
181             return new LayoutPoint(verticalPosition, left);
182         return new LayoutPoint(verticalPosition, right - floatingBox.rect().width());
183     }
184
185     _bottom(floatingStack) {
186         if (!floatingStack || !floatingStack.length)
187             return Number.NaN;
188         let max = Number.NEGATIVE_INFINITY;
189         for (let i = 0; i < floatingStack.length; ++i)
190             max = Math.max(this._formattingContext().absoluteMarginBox(floatingStack[i]).bottom(), max);
191         return max;
192     }
193
194     _formattingContext() {
195         return this.m_formattingContext;
196     }
197 }