• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "config.h"
30 #include "RenderLineBoxList.h"
31 
32 #include "HitTestResult.h"
33 #include "InlineTextBox.h"
34 #include "PaintInfo.h"
35 #include "RenderArena.h"
36 #include "RenderInline.h"
37 #include "RenderView.h"
38 #include "RootInlineBox.h"
39 
40 using namespace std;
41 
42 namespace WebCore {
43 
44 #ifndef NDEBUG
~RenderLineBoxList()45 RenderLineBoxList::~RenderLineBoxList()
46 {
47     ASSERT(!m_firstLineBox);
48     ASSERT(!m_lastLineBox);
49 }
50 #endif
51 
appendLineBox(InlineFlowBox * box)52 void RenderLineBoxList::appendLineBox(InlineFlowBox* box)
53 {
54     checkConsistency();
55 
56     if (!m_firstLineBox)
57         m_firstLineBox = m_lastLineBox = box;
58     else {
59         m_lastLineBox->setNextLineBox(box);
60         box->setPreviousLineBox(m_lastLineBox);
61         m_lastLineBox = box;
62     }
63 
64     checkConsistency();
65 }
66 
deleteLineBoxTree(RenderArena * arena)67 void RenderLineBoxList::deleteLineBoxTree(RenderArena* arena)
68 {
69     InlineFlowBox* line = m_firstLineBox;
70     InlineFlowBox* nextLine;
71     while (line) {
72         nextLine = line->nextLineBox();
73         line->deleteLine(arena);
74         line = nextLine;
75     }
76     m_firstLineBox = m_lastLineBox = 0;
77 }
78 
extractLineBox(InlineFlowBox * box)79 void RenderLineBoxList::extractLineBox(InlineFlowBox* box)
80 {
81     checkConsistency();
82 
83     m_lastLineBox = box->prevLineBox();
84     if (box == m_firstLineBox)
85         m_firstLineBox = 0;
86     if (box->prevLineBox())
87         box->prevLineBox()->setNextLineBox(0);
88     box->setPreviousLineBox(0);
89     for (InlineFlowBox* curr = box; curr; curr = curr->nextLineBox())
90         curr->setExtracted();
91 
92     checkConsistency();
93 }
94 
attachLineBox(InlineFlowBox * box)95 void RenderLineBoxList::attachLineBox(InlineFlowBox* box)
96 {
97     checkConsistency();
98 
99     if (m_lastLineBox) {
100         m_lastLineBox->setNextLineBox(box);
101         box->setPreviousLineBox(m_lastLineBox);
102     } else
103         m_firstLineBox = box;
104     InlineFlowBox* last = box;
105     for (InlineFlowBox* curr = box; curr; curr = curr->nextLineBox()) {
106         curr->setExtracted(false);
107         last = curr;
108     }
109     m_lastLineBox = last;
110 
111     checkConsistency();
112 }
113 
removeLineBox(InlineFlowBox * box)114 void RenderLineBoxList::removeLineBox(InlineFlowBox* box)
115 {
116     checkConsistency();
117 
118     if (box == m_firstLineBox)
119         m_firstLineBox = box->nextLineBox();
120     if (box == m_lastLineBox)
121         m_lastLineBox = box->prevLineBox();
122     if (box->nextLineBox())
123         box->nextLineBox()->setPreviousLineBox(box->prevLineBox());
124     if (box->prevLineBox())
125         box->prevLineBox()->setNextLineBox(box->nextLineBox());
126 
127     checkConsistency();
128 }
129 
deleteLineBoxes(RenderArena * arena)130 void RenderLineBoxList::deleteLineBoxes(RenderArena* arena)
131 {
132     if (m_firstLineBox) {
133         InlineFlowBox* next;
134         for (InlineFlowBox* curr = m_firstLineBox; curr; curr = next) {
135             next = curr->nextLineBox();
136             curr->destroy(arena);
137         }
138         m_firstLineBox = 0;
139         m_lastLineBox = 0;
140     }
141 }
142 
dirtyLineBoxes()143 void RenderLineBoxList::dirtyLineBoxes()
144 {
145     for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox())
146         curr->dirtyLineBoxes();
147 }
148 
rangeIntersectsRect(RenderBoxModelObject * renderer,int logicalTop,int logicalBottom,const IntRect & rect,int tx,int ty) const149 bool RenderLineBoxList::rangeIntersectsRect(RenderBoxModelObject* renderer, int logicalTop, int logicalBottom, const IntRect& rect, int tx, int ty) const
150 {
151     RenderBox* block;
152     if (renderer->isBox())
153         block = toRenderBox(renderer);
154     else
155         block = renderer->containingBlock();
156     int physicalStart = block->flipForWritingMode(logicalTop);
157     int physicalEnd = block->flipForWritingMode(logicalBottom);
158     int physicalExtent = abs(physicalEnd - physicalStart);
159     physicalStart = min(physicalStart, physicalEnd);
160 
161     if (renderer->style()->isHorizontalWritingMode()) {
162         physicalStart += ty;
163         if (physicalStart >= rect.maxY() || physicalStart + physicalExtent <= rect.y())
164             return false;
165     } else {
166         physicalStart += tx;
167         if (physicalStart >= rect.maxX() || physicalStart + physicalExtent <= rect.x())
168             return false;
169     }
170 
171     return true;
172 }
173 
anyLineIntersectsRect(RenderBoxModelObject * renderer,const IntRect & rect,int tx,int ty,bool usePrintRect,int outlineSize) const174 bool RenderLineBoxList::anyLineIntersectsRect(RenderBoxModelObject* renderer, const IntRect& rect, int tx, int ty, bool usePrintRect, int outlineSize) const
175 {
176     // We can check the first box and last box and avoid painting/hit testing if we don't
177     // intersect.  This is a quick short-circuit that we can take to avoid walking any lines.
178     // FIXME: This check is flawed in the following extremely obscure way:
179     // if some line in the middle has a huge overflow, it might actually extend below the last line.
180     RootInlineBox* firstRootBox = firstLineBox()->root();
181     RootInlineBox* lastRootBox = lastLineBox()->root();
182     int firstLineTop = firstLineBox()->logicalTopVisualOverflow(firstRootBox->lineTop());
183     if (usePrintRect && !firstLineBox()->parent())
184         firstLineTop = min(firstLineTop, firstLineBox()->root()->lineTop());
185     int lastLineBottom = lastLineBox()->logicalBottomVisualOverflow(lastRootBox->lineBottom());
186     if (usePrintRect && !lastLineBox()->parent())
187         lastLineBottom = max(lastLineBottom, lastLineBox()->root()->lineBottom());
188     int logicalTop = firstLineTop - outlineSize;
189     int logicalBottom = outlineSize + lastLineBottom;
190 
191     return rangeIntersectsRect(renderer, logicalTop, logicalBottom, rect, tx, ty);
192 }
193 
lineIntersectsDirtyRect(RenderBoxModelObject * renderer,InlineFlowBox * box,const PaintInfo & paintInfo,int tx,int ty) const194 bool RenderLineBoxList::lineIntersectsDirtyRect(RenderBoxModelObject* renderer, InlineFlowBox* box, const PaintInfo& paintInfo, int tx, int ty) const
195 {
196     RootInlineBox* root = box->root();
197     int logicalTop = min(box->logicalTopVisualOverflow(root->lineTop()), root->selectionTop()) - renderer->maximalOutlineSize(paintInfo.phase);
198     int logicalBottom = box->logicalBottomVisualOverflow(root->lineBottom()) + renderer->maximalOutlineSize(paintInfo.phase);
199 
200     return rangeIntersectsRect(renderer, logicalTop, logicalBottom, paintInfo.rect, tx, ty);
201 }
202 
paint(RenderBoxModelObject * renderer,PaintInfo & paintInfo,int tx,int ty) const203 void RenderLineBoxList::paint(RenderBoxModelObject* renderer, PaintInfo& paintInfo, int tx, int ty) const
204 {
205     // Only paint during the foreground/selection phases.
206     if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseOutline
207         && paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines && paintInfo.phase != PaintPhaseTextClip
208         && paintInfo.phase != PaintPhaseMask)
209         return;
210 
211     ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could paint like this is if it has a layer.
212 
213     // If we have no lines then we have no work to do.
214     if (!firstLineBox())
215         return;
216 
217     // FIXME: Paint-time pagination is obsolete and is now only used by embedded WebViews inside AppKit
218     // NSViews.  Do not add any more code for this.
219     RenderView* v = renderer->view();
220     bool usePrintRect = !v->printRect().isEmpty();
221     int outlineSize = renderer->maximalOutlineSize(paintInfo.phase);
222     if (!anyLineIntersectsRect(renderer, paintInfo.rect, tx, ty, usePrintRect, outlineSize))
223         return;
224 
225     PaintInfo info(paintInfo);
226     ListHashSet<RenderInline*> outlineObjects;
227     info.outlineObjects = &outlineObjects;
228 
229     // See if our root lines intersect with the dirty rect.  If so, then we paint
230     // them.  Note that boxes can easily overlap, so we can't make any assumptions
231     // based off positions of our first line box or our last line box.
232     for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) {
233         if (usePrintRect) {
234             // FIXME: This is the deprecated pagination model that is still needed
235             // for embedded views inside AppKit.  AppKit is incapable of paginating vertical
236             // text pages, so we don't have to deal with vertical lines at all here.
237             RootInlineBox* root = curr->root();
238             int topForPaginationCheck = curr->logicalTopVisualOverflow(root->lineTop());
239             int bottomForPaginationCheck = curr->logicalLeftVisualOverflow();
240             if (!curr->parent()) {
241                 // We're a root box.  Use lineTop and lineBottom as well here.
242                 topForPaginationCheck = min(topForPaginationCheck, root->lineTop());
243                 bottomForPaginationCheck = max(bottomForPaginationCheck, root->lineBottom());
244             }
245             if (bottomForPaginationCheck - topForPaginationCheck <= v->printRect().height()) {
246                 if (ty + bottomForPaginationCheck > v->printRect().maxY()) {
247                     if (RootInlineBox* nextRootBox = curr->root()->nextRootBox())
248                         bottomForPaginationCheck = min(bottomForPaginationCheck, min(nextRootBox->logicalTopVisualOverflow(), nextRootBox->lineTop()));
249                 }
250                 if (ty + bottomForPaginationCheck > v->printRect().maxY()) {
251                     if (ty + topForPaginationCheck < v->truncatedAt())
252                         v->setBestTruncatedAt(ty + topForPaginationCheck, renderer);
253                     // If we were able to truncate, don't paint.
254                     if (ty + topForPaginationCheck >= v->truncatedAt())
255                         break;
256                 }
257             }
258         }
259 
260         if (lineIntersectsDirtyRect(renderer, curr, info, tx, ty)) {
261             RootInlineBox* root = curr->root();
262             curr->paint(info, tx, ty, root->lineTop(), root->lineBottom());
263         }
264     }
265 
266     if (info.phase == PaintPhaseOutline || info.phase == PaintPhaseSelfOutline || info.phase == PaintPhaseChildOutlines) {
267         ListHashSet<RenderInline*>::iterator end = info.outlineObjects->end();
268         for (ListHashSet<RenderInline*>::iterator it = info.outlineObjects->begin(); it != end; ++it) {
269             RenderInline* flow = *it;
270             flow->paintOutline(info.context, tx, ty);
271         }
272         info.outlineObjects->clear();
273     }
274 }
275 
276 
hitTest(RenderBoxModelObject * renderer,const HitTestRequest & request,HitTestResult & result,int x,int y,int tx,int ty,HitTestAction hitTestAction) const277 bool RenderLineBoxList::hitTest(RenderBoxModelObject* renderer, const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction) const
278 {
279     if (hitTestAction != HitTestForeground)
280         return false;
281 
282     ASSERT(renderer->isRenderBlock() || (renderer->isRenderInline() && renderer->hasLayer())); // The only way an inline could hit test like this is if it has a layer.
283 
284     // If we have no lines then we have no work to do.
285     if (!firstLineBox())
286         return false;
287 
288     bool isHorizontal = firstLineBox()->isHorizontal();
289 
290     int logicalPointStart = isHorizontal ? y - result.topPadding() : x - result.leftPadding();
291     int logicalPointEnd = (isHorizontal ? y + result.bottomPadding() : x + result.rightPadding()) + 1;
292     IntRect rect(isHorizontal ? x : logicalPointStart, isHorizontal ? logicalPointStart : y,
293                  isHorizontal ? 1 : logicalPointEnd - logicalPointStart,
294                  isHorizontal ? logicalPointEnd - logicalPointStart : 1);
295     if (!anyLineIntersectsRect(renderer, rect, tx, ty))
296         return false;
297 
298     // See if our root lines contain the point.  If so, then we hit test
299     // them further.  Note that boxes can easily overlap, so we can't make any assumptions
300     // based off positions of our first line box or our last line box.
301     for (InlineFlowBox* curr = lastLineBox(); curr; curr = curr->prevLineBox()) {
302         RootInlineBox* root = curr->root();
303         if (rangeIntersectsRect(renderer, curr->logicalTopVisualOverflow(root->lineTop()), curr->logicalBottomVisualOverflow(root->lineBottom()), rect, tx, ty)) {
304             bool inside = curr->nodeAtPoint(request, result, x, y, tx, ty, root->lineTop(), root->lineBottom());
305             if (inside) {
306                 renderer->updateHitTestResult(result, IntPoint(x - tx, y - ty));
307                 return true;
308             }
309         }
310     }
311 
312     return false;
313 }
314 
dirtyLinesFromChangedChild(RenderObject * container,RenderObject * child)315 void RenderLineBoxList::dirtyLinesFromChangedChild(RenderObject* container, RenderObject* child)
316 {
317     if (!container->parent() || (container->isRenderBlock() && (container->selfNeedsLayout() || !container->isBlockFlow())))
318         return;
319 
320     RenderInline* inlineContainer = container->isRenderInline() ? toRenderInline(container) : 0;
321     InlineBox* firstBox = inlineContainer ? inlineContainer->firstLineBoxIncludingCulling() : firstLineBox();
322 
323     // If we have no first line box, then just bail early.
324     if (!firstBox) {
325         // For an empty inline, go ahead and propagate the check up to our parent, unless the parent
326         // is already dirty.
327         if (container->isInline() && !container->parent()->selfNeedsLayout())
328             container->parent()->dirtyLinesFromChangedChild(container);
329         return;
330     }
331 
332     // Try to figure out which line box we belong in.  First try to find a previous
333     // line box by examining our siblings.  If we didn't find a line box, then use our
334     // parent's first line box.
335     RootInlineBox* box = 0;
336     RenderObject* curr = 0;
337     for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) {
338         if (curr->isFloatingOrPositioned())
339             continue;
340 
341         if (curr->isReplaced()) {
342             InlineBox* wrapper = toRenderBox(curr)->inlineBoxWrapper();
343             if (wrapper)
344                 box = wrapper->root();
345         } else if (curr->isText()) {
346             InlineTextBox* textBox = toRenderText(curr)->lastTextBox();
347             if (textBox)
348                 box = textBox->root();
349         } else if (curr->isRenderInline()) {
350             InlineBox* lastSiblingBox = toRenderInline(curr)->lastLineBoxIncludingCulling();
351             if (lastSiblingBox)
352                 box = lastSiblingBox->root();
353         }
354 
355         if (box)
356             break;
357     }
358     if (!box)
359         box = firstBox->root();
360 
361     // If we found a line box, then dirty it.
362     if (box) {
363         RootInlineBox* adjacentBox;
364         box->markDirty();
365 
366         // dirty the adjacent lines that might be affected
367         // NOTE: we dirty the previous line because RootInlineBox objects cache
368         // the address of the first object on the next line after a BR, which we may be
369         // invalidating here.  For more info, see how RenderBlock::layoutInlineChildren
370         // calls setLineBreakInfo with the result of findNextLineBreak.  findNextLineBreak,
371         // despite the name, actually returns the first RenderObject after the BR.
372         // <rdar://problem/3849947> "Typing after pasting line does not appear until after window resize."
373         adjacentBox = box->prevRootBox();
374         if (adjacentBox)
375             adjacentBox->markDirty();
376         adjacentBox = box->nextRootBox();
377         if (adjacentBox && (adjacentBox->lineBreakObj() == child || child->isBR() || (curr && curr->isBR())))
378             adjacentBox->markDirty();
379     }
380 }
381 
382 #ifndef NDEBUG
383 
checkConsistency() const384 void RenderLineBoxList::checkConsistency() const
385 {
386 #ifdef CHECK_CONSISTENCY
387     const InlineFlowBox* prev = 0;
388     for (const InlineFlowBox* child = m_firstLineBox; child != 0; child = child->nextLineBox()) {
389         ASSERT(child->prevLineBox() == prev);
390         prev = child;
391     }
392     ASSERT(prev == m_lastLineBox);
393 #endif
394 }
395 
396 #endif
397 
398 }
399