1/*
2 * Copyright (C) 2011 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 HOLDER "AS IS" AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
21 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
26 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30#include "config.h"
31
32#include "core/rendering/RenderFlowThread.h"
33
34#include "core/dom/Node.h"
35#include "core/rendering/FlowThreadController.h"
36#include "core/rendering/HitTestRequest.h"
37#include "core/rendering/HitTestResult.h"
38#include "core/rendering/PaintInfo.h"
39#include "core/rendering/RenderLayer.h"
40#include "core/rendering/RenderMultiColumnSet.h"
41#include "core/rendering/RenderView.h"
42#include "platform/PODIntervalTree.h"
43#include "platform/geometry/TransformState.h"
44
45namespace blink {
46
47RenderFlowThread::RenderFlowThread()
48    : RenderBlockFlow(0)
49    , m_regionsInvalidated(false)
50    , m_regionsHaveUniformLogicalHeight(true)
51    , m_pageLogicalSizeChanged(false)
52{
53    setFlowThreadState(InsideOutOfFlowThread);
54}
55
56void RenderFlowThread::removeRegionFromThread(RenderMultiColumnSet* columnSet)
57{
58    ASSERT(columnSet);
59    m_multiColumnSetList.remove(columnSet);
60}
61
62void RenderFlowThread::invalidateRegions()
63{
64    if (m_regionsInvalidated) {
65        ASSERT(selfNeedsLayout());
66        return;
67    }
68
69    setNeedsLayoutAndFullPaintInvalidation();
70
71    m_regionsInvalidated = true;
72}
73
74class CurrentRenderFlowThreadDisabler {
75    WTF_MAKE_NONCOPYABLE(CurrentRenderFlowThreadDisabler);
76public:
77    CurrentRenderFlowThreadDisabler(RenderView* view)
78        : m_view(view)
79        , m_renderFlowThread(0)
80    {
81        m_renderFlowThread = m_view->flowThreadController()->currentRenderFlowThread();
82        if (m_renderFlowThread)
83            view->flowThreadController()->setCurrentRenderFlowThread(0);
84    }
85    ~CurrentRenderFlowThreadDisabler()
86    {
87        if (m_renderFlowThread)
88            m_view->flowThreadController()->setCurrentRenderFlowThread(m_renderFlowThread);
89    }
90private:
91    RenderView* m_view;
92    RenderFlowThread* m_renderFlowThread;
93};
94
95void RenderFlowThread::validateRegions()
96{
97    if (m_regionsInvalidated) {
98        m_regionsInvalidated = false;
99        m_regionsHaveUniformLogicalHeight = true;
100
101        if (hasRegions()) {
102            LayoutUnit previousRegionLogicalHeight = 0;
103            bool firstRegionVisited = false;
104
105            for (RenderMultiColumnSetList::iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
106                RenderMultiColumnSet* columnSet = *iter;
107                LayoutUnit regionLogicalHeight = columnSet->pageLogicalHeight();
108
109                if (!firstRegionVisited) {
110                    firstRegionVisited = true;
111                } else {
112                    if (m_regionsHaveUniformLogicalHeight && previousRegionLogicalHeight != regionLogicalHeight)
113                        m_regionsHaveUniformLogicalHeight = false;
114                }
115
116                previousRegionLogicalHeight = regionLogicalHeight;
117            }
118        }
119    }
120
121    updateLogicalWidth(); // Called to get the maximum logical width for the columnSet.
122    updateRegionsFlowThreadPortionRect();
123}
124
125void RenderFlowThread::layout()
126{
127    m_pageLogicalSizeChanged = m_regionsInvalidated && everHadLayout();
128
129    CurrentRenderFlowThreadMaintainer currentFlowThreadSetter(this);
130    RenderBlockFlow::layout();
131
132    m_pageLogicalSizeChanged = false;
133}
134
135void RenderFlowThread::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
136{
137    computedValues.m_position = logicalTop;
138    computedValues.m_extent = 0;
139
140    for (RenderMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
141        RenderMultiColumnSet* columnSet = *iter;
142        computedValues.m_extent += columnSet->logicalHeightOfAllFlowThreadContent();
143    }
144}
145
146bool RenderFlowThread::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
147{
148    if (hitTestAction == HitTestBlockBackground)
149        return false;
150    return RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction);
151}
152
153bool RenderFlowThread::shouldIssuePaintInvalidations(const LayoutRect& r) const
154{
155    if (view()->document().printing() || r.isEmpty())
156        return false;
157
158    return true;
159}
160
161void RenderFlowThread::paintInvalidationRectangleInRegions(const LayoutRect& paintInvalidationRect) const
162{
163    if (!shouldIssuePaintInvalidations(paintInvalidationRect) || !hasValidRegionInfo())
164        return;
165
166    // We can't use currentFlowThread as it is possible to have interleaved flow threads and the wrong one could be used.
167    // Let each columnSet figure out the proper enclosing flow thread.
168    CurrentRenderFlowThreadDisabler disabler(view());
169
170    for (RenderMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
171        RenderMultiColumnSet* columnSet = *iter;
172
173        columnSet->paintInvalidationForFlowThreadContent(paintInvalidationRect);
174    }
175}
176
177LayoutUnit RenderFlowThread::pageLogicalHeightForOffset(LayoutUnit offset)
178{
179    RenderMultiColumnSet* columnSet = columnSetAtBlockOffset(offset);
180    if (!columnSet)
181        return 0;
182
183    return columnSet->pageLogicalHeight();
184}
185
186LayoutUnit RenderFlowThread::pageRemainingLogicalHeightForOffset(LayoutUnit offset, PageBoundaryRule pageBoundaryRule)
187{
188    RenderMultiColumnSet* columnSet = columnSetAtBlockOffset(offset);
189    if (!columnSet)
190        return 0;
191
192    LayoutUnit pageLogicalTop = columnSet->pageLogicalTopForOffset(offset);
193    LayoutUnit pageLogicalHeight = columnSet->pageLogicalHeight();
194    LayoutUnit pageLogicalBottom = pageLogicalTop + pageLogicalHeight;
195    LayoutUnit remainingHeight = pageLogicalBottom - offset;
196    if (pageBoundaryRule == IncludePageBoundary) {
197        // If IncludePageBoundary is set, the line exactly on the top edge of a
198        // columnSet will act as being part of the previous columnSet.
199        remainingHeight = intMod(remainingHeight, pageLogicalHeight);
200    }
201    return remainingHeight;
202}
203
204RenderRegion* RenderFlowThread::firstRegion() const
205{
206    if (!hasValidRegionInfo())
207        return 0;
208    return m_multiColumnSetList.first();
209}
210
211RenderRegion* RenderFlowThread::lastRegion() const
212{
213    if (!hasValidRegionInfo())
214        return 0;
215    return m_multiColumnSetList.last();
216}
217
218void RenderFlowThread::updateRegionsFlowThreadPortionRect()
219{
220    LayoutUnit logicalHeight = 0;
221    // FIXME: Optimize not to clear the interval all the time. This implies manually managing the tree nodes lifecycle.
222    m_multiColumnSetIntervalTree.clear();
223    m_multiColumnSetIntervalTree.initIfNeeded();
224    for (RenderMultiColumnSetList::iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
225        RenderMultiColumnSet* columnSet = *iter;
226
227        LayoutUnit columnSetLogicalWidth = columnSet->pageLogicalWidth();
228        LayoutUnit columnSetLogicalHeight = std::min<LayoutUnit>(RenderFlowThread::maxLogicalHeight() - logicalHeight, columnSet->logicalHeightOfAllFlowThreadContent());
229
230        LayoutRect columnSetRect(style()->direction() == LTR ? LayoutUnit() : logicalWidth() - columnSetLogicalWidth, logicalHeight, columnSetLogicalWidth, columnSetLogicalHeight);
231
232        columnSet->setFlowThreadPortionRect(isHorizontalWritingMode() ? columnSetRect : columnSetRect.transposedRect());
233
234        m_multiColumnSetIntervalTree.add(MultiColumnSetIntervalTree::createInterval(logicalHeight, logicalHeight + columnSetLogicalHeight, columnSet));
235
236        logicalHeight += columnSetLogicalHeight;
237    }
238}
239
240void RenderFlowThread::collectLayerFragments(LayerFragments& layerFragments, const LayoutRect& layerBoundingBox, const LayoutRect& dirtyRect)
241{
242    ASSERT(!m_regionsInvalidated);
243
244    for (RenderMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
245        RenderMultiColumnSet* columnSet = *iter;
246        columnSet->collectLayerFragments(layerFragments, layerBoundingBox, dirtyRect);
247    }
248}
249
250LayoutRect RenderFlowThread::fragmentsBoundingBox(const LayoutRect& layerBoundingBox)
251{
252    ASSERT(!m_regionsInvalidated);
253
254    LayoutRect result;
255    for (RenderMultiColumnSetList::const_iterator iter = m_multiColumnSetList.begin(); iter != m_multiColumnSetList.end(); ++iter) {
256        RenderMultiColumnSet* columnSet = *iter;
257        LayerFragments fragments;
258        columnSet->collectLayerFragments(fragments, layerBoundingBox, PaintInfo::infiniteRect());
259        for (size_t i = 0; i < fragments.size(); ++i) {
260            const LayerFragment& fragment = fragments.at(i);
261            LayoutRect fragmentRect(layerBoundingBox);
262            fragmentRect.intersect(fragment.paginationClip);
263            fragmentRect.moveBy(fragment.paginationOffset);
264            result.unite(fragmentRect);
265        }
266    }
267
268    return result;
269}
270
271bool RenderFlowThread::cachedOffsetFromLogicalTopOfFirstRegion(const RenderBox* box, LayoutUnit& result) const
272{
273    RenderBoxToOffsetMap::const_iterator offsetIterator = m_boxesToOffsetMap.find(box);
274    if (offsetIterator == m_boxesToOffsetMap.end())
275        return false;
276
277    result = offsetIterator->value;
278    return true;
279}
280
281void RenderFlowThread::setOffsetFromLogicalTopOfFirstRegion(const RenderBox* box, LayoutUnit offset)
282{
283    m_boxesToOffsetMap.set(box, offset);
284}
285
286void RenderFlowThread::clearOffsetFromLogicalTopOfFirstRegion(const RenderBox* box)
287{
288    ASSERT(m_boxesToOffsetMap.contains(box));
289    m_boxesToOffsetMap.remove(box);
290}
291
292const RenderBox* RenderFlowThread::currentStatePusherRenderBox() const
293{
294    const RenderObject* currentObject = m_statePusherObjectsStack.isEmpty() ? 0 : m_statePusherObjectsStack.last();
295    if (currentObject && currentObject->isBox())
296        return toRenderBox(currentObject);
297
298    return 0;
299}
300
301void RenderFlowThread::pushFlowThreadLayoutState(const RenderObject& object)
302{
303    if (const RenderBox* currentBoxDescendant = currentStatePusherRenderBox()) {
304        LayoutState* layoutState = currentBoxDescendant->view()->layoutState();
305        if (layoutState && layoutState->isPaginated()) {
306            ASSERT(layoutState->renderer() == currentBoxDescendant);
307            LayoutSize offsetDelta = layoutState->layoutOffset() - layoutState->pageOffset();
308            setOffsetFromLogicalTopOfFirstRegion(currentBoxDescendant, currentBoxDescendant->isHorizontalWritingMode() ? offsetDelta.height() : offsetDelta.width());
309        }
310    }
311
312    m_statePusherObjectsStack.add(&object);
313}
314
315void RenderFlowThread::popFlowThreadLayoutState()
316{
317    m_statePusherObjectsStack.removeLast();
318
319    if (const RenderBox* currentBoxDescendant = currentStatePusherRenderBox()) {
320        LayoutState* layoutState = currentBoxDescendant->view()->layoutState();
321        if (layoutState && layoutState->isPaginated())
322            clearOffsetFromLogicalTopOfFirstRegion(currentBoxDescendant);
323    }
324}
325
326LayoutUnit RenderFlowThread::offsetFromLogicalTopOfFirstRegion(const RenderBlock* currentBlock) const
327{
328    // First check if we cached the offset for the block if it's an ancestor containing block of the box
329    // being currently laid out.
330    LayoutUnit offset;
331    if (cachedOffsetFromLogicalTopOfFirstRegion(currentBlock, offset))
332        return offset;
333
334    // If it's the current box being laid out, use the layout state.
335    const RenderBox* currentBoxDescendant = currentStatePusherRenderBox();
336    if (currentBlock == currentBoxDescendant) {
337        LayoutState* layoutState = view()->layoutState();
338        ASSERT(layoutState->renderer() == currentBlock);
339        ASSERT(layoutState && layoutState->isPaginated());
340        LayoutSize offsetDelta = layoutState->layoutOffset() - layoutState->pageOffset();
341        return currentBoxDescendant->isHorizontalWritingMode() ? offsetDelta.height() : offsetDelta.width();
342    }
343
344    // As a last resort, take the slow path.
345    LayoutRect blockRect(0, 0, currentBlock->width(), currentBlock->height());
346    while (currentBlock && !currentBlock->isRenderFlowThread()) {
347        RenderBlock* containerBlock = currentBlock->containingBlock();
348        ASSERT(containerBlock);
349        if (!containerBlock)
350            return 0;
351        LayoutPoint currentBlockLocation = currentBlock->location();
352
353        if (containerBlock->style()->writingMode() != currentBlock->style()->writingMode()) {
354            // We have to put the block rect in container coordinates
355            // and we have to take into account both the container and current block flipping modes
356            if (containerBlock->style()->isFlippedBlocksWritingMode()) {
357                if (containerBlock->isHorizontalWritingMode())
358                    blockRect.setY(currentBlock->height() - blockRect.maxY());
359                else
360                    blockRect.setX(currentBlock->width() - blockRect.maxX());
361            }
362            currentBlock->flipForWritingMode(blockRect);
363        }
364        blockRect.moveBy(currentBlockLocation);
365        currentBlock = containerBlock;
366    }
367
368    return currentBlock->isHorizontalWritingMode() ? blockRect.y() : blockRect.x();
369}
370
371void RenderFlowThread::RegionSearchAdapter::collectIfNeeded(const MultiColumnSetInterval& interval)
372{
373    if (m_result)
374        return;
375    if (interval.low() <= m_offset && interval.high() > m_offset)
376        m_result = interval.data();
377}
378
379CurrentRenderFlowThreadMaintainer::CurrentRenderFlowThreadMaintainer(RenderFlowThread* renderFlowThread)
380    : m_renderFlowThread(renderFlowThread)
381    , m_previousRenderFlowThread(0)
382{
383    if (!m_renderFlowThread)
384        return;
385    RenderView* view = m_renderFlowThread->view();
386    m_previousRenderFlowThread = view->flowThreadController()->currentRenderFlowThread();
387    view->flowThreadController()->setCurrentRenderFlowThread(m_renderFlowThread);
388}
389
390CurrentRenderFlowThreadMaintainer::~CurrentRenderFlowThreadMaintainer()
391{
392    if (!m_renderFlowThread)
393        return;
394    RenderView* view = m_renderFlowThread->view();
395    ASSERT(view->flowThreadController()->currentRenderFlowThread() == m_renderFlowThread);
396    view->flowThreadController()->setCurrentRenderFlowThread(m_previousRenderFlowThread);
397}
398
399
400} // namespace blink
401