[CSS Shapes] Support block content with inline content around floats in shape-inside
[WebKit-https.git] / Source / WebCore / rendering / LineWidth.cpp
1 /*
2  * Copyright (C) 2013 Adobe Systems Incorporated. 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  *
8  * 1. Redistributions of source code must retain the above
9  *    copyright notice, this list of conditions and the following
10  *    disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  *    copyright notice, this list of conditions and the following
13  *    disclaimer in the documentation and/or other materials
14  *    provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
20  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27  * OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "config.h"
31 #include "LineWidth.h"
32
33 #include "RenderBlock.h"
34 #include "RenderRubyRun.h"
35
36 namespace WebCore {
37
38 LineWidth::LineWidth(RenderBlock& block, bool isFirstLine, IndentTextOrNot shouldIndentText)
39     : m_block(block)
40     , m_uncommittedWidth(0)
41     , m_committedWidth(0)
42     , m_overhangWidth(0)
43     , m_trailingWhitespaceWidth(0)
44     , m_trailingCollapsedWhitespaceWidth(0)
45     , m_left(0)
46     , m_right(0)
47     , m_availableWidth(0)
48 #if ENABLE(CSS_SHAPES)
49     , m_segment(0)
50 #endif
51     , m_isFirstLine(isFirstLine)
52     , m_shouldIndentText(shouldIndentText)
53 {
54 #if ENABLE(CSS_SHAPES)
55     updateCurrentShapeSegment();
56 #endif
57     updateAvailableWidth();
58 }
59
60 bool LineWidth::fitsOnLine(bool ignoringTrailingSpace) const
61 {
62     return ignoringTrailingSpace ? fitsOnLineExcludingTrailingCollapsedWhitespace() : fitsOnLineIncludingExtraWidth(0);
63 }
64
65 bool LineWidth::fitsOnLineIncludingExtraWidth(float extra) const
66 {
67     return currentWidth() + extra <= m_availableWidth;
68 }
69
70 bool LineWidth::fitsOnLineExcludingTrailingWhitespace(float extra) const
71 {
72     return currentWidth() - m_trailingWhitespaceWidth + extra <= m_availableWidth;
73 }
74
75 void LineWidth::updateAvailableWidth(LayoutUnit replacedHeight)
76 {
77     LayoutUnit height = m_block.logicalHeight();
78     LayoutUnit logicalHeight = m_block.minLineHeightForReplacedRenderer(m_isFirstLine, replacedHeight);
79     m_left = m_block.logicalLeftOffsetForLine(height, shouldIndentText(), logicalHeight);
80     m_right = m_block.logicalRightOffsetForLine(height, shouldIndentText(), logicalHeight);
81
82 #if ENABLE(CSS_SHAPES)
83     if (m_segment) {
84         m_left = std::max<float>(m_segment->logicalLeft, m_left);
85         m_right = std::min<float>(m_segment->logicalRight, m_right);
86     }
87 #endif
88
89     computeAvailableWidthFromLeftAndRight();
90 }
91
92 void LineWidth::shrinkAvailableWidthForNewFloatIfNeeded(FloatingObject* newFloat)
93 {
94     LayoutUnit height = m_block.logicalHeight();
95     if (height < newFloat->logicalTop(m_block.isHorizontalWritingMode()) || height >= newFloat->logicalBottom(m_block.isHorizontalWritingMode()))
96         return;
97
98 #if ENABLE(CSS_SHAPES)
99     // When floats with shape outside are stacked, the floats are positioned based on the margin box of the float,
100     // not the shape's contour. Since we computed the width based on the shape contour when we added the float,
101     // when we add a subsequent float on the same line, we need to undo the shape delta in order to position
102     // based on the margin box. In order to do this, we need to walk back through the floating object list to find
103     // the first previous float that is on the same side as our newFloat.
104     ShapeOutsideInfo* previousShapeOutsideInfo = 0;
105     const FloatingObjectSet& floatingObjectSet = m_block.m_floatingObjects->set();
106     auto it = floatingObjectSet.end();
107     auto begin = floatingObjectSet.begin();
108     LayoutUnit lineHeight = m_block.lineHeight(m_isFirstLine, m_block.isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes);
109     for (--it; it != begin; --it) {
110         FloatingObject* previousFloat = it->get();
111         if (previousFloat != newFloat && previousFloat->type() == newFloat->type()) {
112             previousShapeOutsideInfo = previousFloat->renderer().shapeOutsideInfo();
113             if (previousShapeOutsideInfo)
114                 previousShapeOutsideInfo->updateDeltasForContainingBlockLine(&m_block, previousFloat, m_block.logicalHeight(), lineHeight);
115             break;
116         }
117     }
118
119     ShapeOutsideInfo* shapeOutsideInfo = newFloat->renderer().shapeOutsideInfo();
120     if (shapeOutsideInfo)
121         shapeOutsideInfo->updateDeltasForContainingBlockLine(&m_block, newFloat, m_block.logicalHeight(), lineHeight);
122 #endif
123
124     if (newFloat->type() == FloatingObject::FloatLeft) {
125         float newLeft = newFloat->logicalRight(m_block.isHorizontalWritingMode());
126 #if ENABLE(CSS_SHAPES)
127         if (previousShapeOutsideInfo)
128             newLeft -= previousShapeOutsideInfo->rightMarginBoxDelta();
129         if (shapeOutsideInfo)
130             newLeft += shapeOutsideInfo->rightMarginBoxDelta();
131 #endif
132
133         if (shouldIndentText() && m_block.style()->isLeftToRightDirection())
134             newLeft += floorToInt(m_block.textIndentOffset());
135         m_left = std::max<float>(m_left, newLeft);
136     } else {
137         float newRight = newFloat->logicalLeft(m_block.isHorizontalWritingMode());
138 #if ENABLE(CSS_SHAPES)
139         if (previousShapeOutsideInfo)
140             newRight -= previousShapeOutsideInfo->leftMarginBoxDelta();
141         if (shapeOutsideInfo)
142             newRight += shapeOutsideInfo->leftMarginBoxDelta();
143 #endif
144
145         if (shouldIndentText() && !m_block.style()->isLeftToRightDirection())
146             newRight -= floorToInt(m_block.textIndentOffset());
147         m_right = std::min<float>(m_right, newRight);
148     }
149
150     computeAvailableWidthFromLeftAndRight();
151 }
152
153 float LineWidth::uncommittedWidthForObject(const RenderObject& object) const
154 {
155     auto result = m_uncommittedWidthMap.find(&object);
156     if (result != m_uncommittedWidthMap.end())
157         return result->value;
158     return -1;
159 }
160
161 void LineWidth::addUncommittedWidth(float delta, const RenderObject& current)
162 {
163     m_uncommittedWidth += delta;
164
165     auto result = m_uncommittedWidthMap.add(&current, delta);
166     if (!result.isNewEntry)
167         result.iterator->value += delta;
168 }
169
170 void LineWidth::commit()
171 {
172     m_committedWidth += m_uncommittedWidth;
173     m_uncommittedWidth = 0;
174 }
175
176 void LineWidth::applyOverhang(RenderRubyRun* rubyRun, RenderObject* startRenderer, RenderObject* endRenderer)
177 {
178     int startOverhang;
179     int endOverhang;
180     rubyRun->getOverhang(m_isFirstLine, startRenderer, endRenderer, startOverhang, endOverhang);
181
182     startOverhang = std::min<int>(startOverhang, m_committedWidth);
183     m_availableWidth += startOverhang;
184
185     endOverhang = std::max(std::min<int>(endOverhang, m_availableWidth - currentWidth()), 0);
186     m_availableWidth += endOverhang;
187     m_overhangWidth += startOverhang + endOverhang;
188 }
189
190 void LineWidth::fitBelowFloats()
191 {
192     ASSERT(!m_committedWidth);
193     ASSERT(!fitsOnLine());
194
195     LayoutUnit floatLogicalBottom;
196     LayoutUnit lastFloatLogicalBottom = m_block.logicalHeight();
197     float newLineWidth = m_availableWidth;
198     float newLineLeft = m_left;
199     float newLineRight = m_right;
200     while (true) {
201         floatLogicalBottom = m_block.nextFloatLogicalBottomBelow(lastFloatLogicalBottom, ShapeOutsideFloatShapeOffset);
202         if (floatLogicalBottom <= lastFloatLogicalBottom)
203             break;
204
205         newLineLeft = m_block.logicalLeftOffsetForLine(floatLogicalBottom, shouldIndentText());
206         newLineRight = m_block.logicalRightOffsetForLine(floatLogicalBottom, shouldIndentText());
207         newLineWidth = max(0.0f, newLineRight - newLineLeft);
208         lastFloatLogicalBottom = floatLogicalBottom;
209
210 #if ENABLE(CSS_SHAPES)
211         // FIXME: This code should be refactored to incorporate with the code above.
212         ShapeInsideInfo* shapeInsideInfo = m_block.layoutShapeInsideInfo();
213         if (shapeInsideInfo) {
214             LayoutUnit logicalOffsetFromShapeContainer = m_block.logicalOffsetFromShapeAncestorContainer(shapeInsideInfo->owner()).height();
215             LayoutUnit lineHeight = m_block.lineHeight(false, m_block.isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes);
216             shapeInsideInfo->updateSegmentsForLine(lastFloatLogicalBottom + logicalOffsetFromShapeContainer, lineHeight);
217             updateCurrentShapeSegment();
218             updateAvailableWidth();
219         }
220 #endif
221         if (newLineWidth >= m_uncommittedWidth)
222             break;
223     }
224
225     if (newLineWidth > m_availableWidth) {
226         m_block.setLogicalHeight(lastFloatLogicalBottom);
227         m_availableWidth = newLineWidth + m_overhangWidth;
228         m_left = newLineLeft;
229         m_right = newLineRight;
230     }
231 }
232
233 void LineWidth::setTrailingWhitespaceWidth(float collapsedWhitespace, float borderPaddingMargin)
234 {
235     m_trailingCollapsedWhitespaceWidth = collapsedWhitespace;
236     m_trailingWhitespaceWidth = collapsedWhitespace + borderPaddingMargin;
237 }
238
239 #if ENABLE(CSS_SHAPES)
240 void LineWidth::updateCurrentShapeSegment()
241 {
242     if (ShapeInsideInfo* shapeInsideInfo = m_block.layoutShapeInsideInfo())
243         m_segment = shapeInsideInfo->currentSegment();
244 }
245 #endif
246
247 void LineWidth::computeAvailableWidthFromLeftAndRight()
248 {
249     m_availableWidth = max(0.0f, m_right - m_left) + m_overhangWidth;
250 }
251
252 bool LineWidth::fitsOnLineExcludingTrailingCollapsedWhitespace() const
253 {
254     return currentWidth() - m_trailingCollapsedWhitespaceWidth <= m_availableWidth;
255 }
256
257 }