Home | History | Annotate | Download | only in rendering
      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
     45 RenderLineBoxList::~RenderLineBoxList()
     46 {
     47     ASSERT(!m_firstLineBox);
     48     ASSERT(!m_lastLineBox);
     49 }
     50 #endif
     51 
     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 
     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 
     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 
     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 
    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 
    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 
    143 void RenderLineBoxList::dirtyLineBoxes()
    144 {
    145     for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox())
    146         curr->dirtyLineBoxes();
    147 }
    148 
    149 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 
    174 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 
    194 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 
    203 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 
    277 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 
    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 
    384 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