Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2003, 2006, 2008 Apple Inc. All rights reserved.
      3  *
      4  * This library is free software; you can redistribute it and/or
      5  * modify it under the terms of the GNU Library General Public
      6  * License as published by the Free Software Foundation; either
      7  * version 2 of the License, or (at your option) any later version.
      8  *
      9  * This library is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     12  * Library General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU Library General Public License
     15  * along with this library; see the file COPYING.LIB.  If not, write to
     16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     17  * Boston, MA 02110-1301, USA.
     18  */
     19 
     20 #include "config.h"
     21 #include "RootInlineBox.h"
     22 
     23 #include "BidiResolver.h"
     24 #include "Chrome.h"
     25 #include "ChromeClient.h"
     26 #include "Document.h"
     27 #include "EllipsisBox.h"
     28 #include "Frame.h"
     29 #include "GraphicsContext.h"
     30 #include "HitTestResult.h"
     31 #include "Page.h"
     32 #include "RenderArena.h"
     33 #include "RenderBlock.h"
     34 
     35 using namespace std;
     36 
     37 namespace WebCore {
     38 
     39 typedef WTF::HashMap<const RootInlineBox*, EllipsisBox*> EllipsisBoxMap;
     40 static EllipsisBoxMap* gEllipsisBoxMap = 0;
     41 
     42 void RootInlineBox::destroy(RenderArena* arena)
     43 {
     44     detachEllipsisBox(arena);
     45     InlineFlowBox::destroy(arena);
     46 }
     47 
     48 void RootInlineBox::detachEllipsisBox(RenderArena* arena)
     49 {
     50     if (m_hasEllipsisBox) {
     51         EllipsisBox* box = gEllipsisBoxMap->take(this);
     52         box->setParent(0);
     53         box->destroy(arena);
     54         m_hasEllipsisBox = false;
     55     }
     56 }
     57 
     58 RenderLineBoxList* RootInlineBox::rendererLineBoxes() const
     59 {
     60     return block()->lineBoxes();
     61 }
     62 
     63 void RootInlineBox::clearTruncation()
     64 {
     65     if (m_hasEllipsisBox) {
     66         detachEllipsisBox(renderer()->renderArena());
     67         InlineFlowBox::clearTruncation();
     68     }
     69 }
     70 
     71 bool RootInlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth)
     72 {
     73     // First sanity-check the unoverflowed width of the whole line to see if there is sufficient room.
     74     int delta = ltr ? lineBoxEdge - blockEdge : blockEdge - lineBoxEdge;
     75     if (width() - delta < ellipsisWidth)
     76         return false;
     77 
     78     // Next iterate over all the line boxes on the line.  If we find a replaced element that intersects
     79     // then we refuse to accommodate the ellipsis.  Otherwise we're ok.
     80     return InlineFlowBox::canAccommodateEllipsis(ltr, blockEdge, ellipsisWidth);
     81 }
     82 
     83 void RootInlineBox::placeEllipsis(const AtomicString& ellipsisStr,  bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth,
     84                                   InlineBox* markupBox)
     85 {
     86     // Create an ellipsis box.
     87     EllipsisBox* ellipsisBox = new (renderer()->renderArena()) EllipsisBox(renderer(), ellipsisStr, this,
     88                                                               ellipsisWidth - (markupBox ? markupBox->width() : 0), height(),
     89                                                               y(), !prevRootBox(),
     90                                                               markupBox);
     91 
     92     if (!gEllipsisBoxMap)
     93         gEllipsisBoxMap = new EllipsisBoxMap();
     94     gEllipsisBoxMap->add(this, ellipsisBox);
     95     m_hasEllipsisBox = true;
     96 
     97     // FIXME: Do we need an RTL version of this?
     98     if (ltr && (x() + width() + ellipsisWidth) <= blockRightEdge) {
     99         ellipsisBox->m_x = x() + width();
    100         return;
    101     }
    102 
    103     // Now attempt to find the nearest glyph horizontally and place just to the right (or left in RTL)
    104     // of that glyph.  Mark all of the objects that intersect the ellipsis box as not painting (as being
    105     // truncated).
    106     bool foundBox = false;
    107     ellipsisBox->m_x = placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox);
    108 }
    109 
    110 int RootInlineBox::placeEllipsisBox(bool ltr, int blockLeftEdge, int blockRightEdge, int ellipsisWidth, bool& foundBox)
    111 {
    112     int result = InlineFlowBox::placeEllipsisBox(ltr, blockLeftEdge, blockRightEdge, ellipsisWidth, foundBox);
    113     if (result == -1)
    114         result = ltr ? blockRightEdge - ellipsisWidth : blockLeftEdge;
    115     return result;
    116 }
    117 
    118 void RootInlineBox::paintEllipsisBox(RenderObject::PaintInfo& paintInfo, int tx, int ty) const
    119 {
    120     if (m_hasEllipsisBox && renderer()->shouldPaintWithinRoot(paintInfo) && renderer()->style()->visibility() == VISIBLE &&
    121             paintInfo.phase == PaintPhaseForeground)
    122         ellipsisBox()->paint(paintInfo, tx, ty);
    123 }
    124 
    125 #if PLATFORM(MAC)
    126 
    127 void RootInlineBox::addHighlightOverflow()
    128 {
    129     Frame* frame = renderer()->document()->frame();
    130     if (!frame)
    131         return;
    132     Page* page = frame->page();
    133     if (!page)
    134         return;
    135 
    136     // Highlight acts as a selection inflation.
    137     FloatRect rootRect(0, selectionTop(), width(), selectionHeight());
    138     IntRect inflatedRect = enclosingIntRect(page->chrome()->client()->customHighlightRect(renderer()->node(), renderer()->style()->highlight(), rootRect));
    139     setHorizontalOverflowPositions(leftLayoutOverflow(), rightLayoutOverflow(), min(leftVisualOverflow(), inflatedRect.x()), max(rightVisualOverflow(), inflatedRect.right()));
    140     setVerticalOverflowPositions(topLayoutOverflow(), bottomLayoutOverflow(), min(topVisualOverflow(), inflatedRect.y()), max(bottomVisualOverflow(), inflatedRect.bottom()), height());
    141 }
    142 
    143 void RootInlineBox::paintCustomHighlight(RenderObject::PaintInfo& paintInfo, int tx, int ty, const AtomicString& highlightType)
    144 {
    145     if (!renderer()->shouldPaintWithinRoot(paintInfo) || renderer()->style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseForeground)
    146         return;
    147 
    148     Frame* frame = renderer()->document()->frame();
    149     if (!frame)
    150         return;
    151     Page* page = frame->page();
    152     if (!page)
    153         return;
    154 
    155     // Get the inflated rect so that we can properly hit test.
    156     FloatRect rootRect(tx + x(), ty + selectionTop(), width(), selectionHeight());
    157     FloatRect inflatedRect = page->chrome()->client()->customHighlightRect(renderer()->node(), highlightType, rootRect);
    158     if (inflatedRect.intersects(paintInfo.rect))
    159         page->chrome()->client()->paintCustomHighlight(renderer()->node(), highlightType, rootRect, rootRect, false, true);
    160 }
    161 
    162 #endif
    163 
    164 void RootInlineBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty)
    165 {
    166     InlineFlowBox::paint(paintInfo, tx, ty);
    167     paintEllipsisBox(paintInfo, tx, ty);
    168 #if PLATFORM(MAC)
    169     RenderStyle* styleToUse = renderer()->style(m_firstLine);
    170     if (styleToUse->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
    171         paintCustomHighlight(paintInfo, tx, ty, styleToUse->highlight());
    172 #endif
    173 }
    174 
    175 bool RootInlineBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty)
    176 {
    177     if (m_hasEllipsisBox && visibleToHitTesting()) {
    178         if (ellipsisBox()->nodeAtPoint(request, result, x, y, tx, ty)) {
    179             renderer()->updateHitTestResult(result, IntPoint(x - tx, y - ty));
    180             return true;
    181         }
    182     }
    183     return InlineFlowBox::nodeAtPoint(request, result, x, y, tx, ty);
    184 }
    185 
    186 void RootInlineBox::adjustPosition(int dx, int dy)
    187 {
    188     InlineFlowBox::adjustPosition(dx, dy);
    189     m_lineTop += dy;
    190     m_lineBottom += dy;
    191     m_blockHeight += dy;
    192 }
    193 
    194 void RootInlineBox::childRemoved(InlineBox* box)
    195 {
    196     if (box->renderer() == m_lineBreakObj)
    197         setLineBreakInfo(0, 0, BidiStatus());
    198 
    199     for (RootInlineBox* prev = prevRootBox(); prev && prev->lineBreakObj() == box->renderer(); prev = prev->prevRootBox()) {
    200         prev->setLineBreakInfo(0, 0, BidiStatus());
    201         prev->markDirty();
    202     }
    203 }
    204 
    205 int RootInlineBox::verticallyAlignBoxes(int heightOfBlock)
    206 {
    207     int maxPositionTop = 0;
    208     int maxPositionBottom = 0;
    209     int maxAscent = 0;
    210     int maxDescent = 0;
    211 
    212     // Figure out if we're in strict mode.  Note that we can't simply use !style()->htmlHacks(),
    213     // because that would match almost strict mode as well.
    214     RenderObject* curr = renderer();
    215     while (curr && !curr->node())
    216         curr = curr->container();
    217     bool strictMode = (curr && curr->document()->inStrictMode());
    218 
    219     computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode);
    220 
    221     if (maxAscent + maxDescent < max(maxPositionTop, maxPositionBottom))
    222         adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom);
    223 
    224     int maxHeight = maxAscent + maxDescent;
    225     int lineTop = heightOfBlock;
    226     int lineBottom = heightOfBlock;
    227     placeBoxesVertically(heightOfBlock, maxHeight, maxAscent, strictMode, lineTop, lineBottom);
    228     computeVerticalOverflow(lineTop, lineBottom, strictMode);
    229     setLineTopBottomPositions(lineTop, lineBottom);
    230 
    231     heightOfBlock += maxHeight;
    232 
    233     return heightOfBlock;
    234 }
    235 
    236 GapRects RootInlineBox::fillLineSelectionGap(int selTop, int selHeight, RenderBlock* rootBlock, int blockX, int blockY, int tx, int ty,
    237                                              const RenderObject::PaintInfo* paintInfo)
    238 {
    239     RenderObject::SelectionState lineState = selectionState();
    240 
    241     bool leftGap, rightGap;
    242     block()->getHorizontalSelectionGapInfo(lineState, leftGap, rightGap);
    243 
    244     GapRects result;
    245 
    246     InlineBox* firstBox = firstSelectedBox();
    247     InlineBox* lastBox = lastSelectedBox();
    248     if (leftGap)
    249         result.uniteLeft(block()->fillLeftSelectionGap(firstBox->parent()->renderer(),
    250                                                        firstBox->x(), selTop, selHeight,
    251                                                        rootBlock, blockX, blockY, tx, ty, paintInfo));
    252     if (rightGap)
    253         result.uniteRight(block()->fillRightSelectionGap(lastBox->parent()->renderer(),
    254                                                          lastBox->x() + lastBox->width(), selTop, selHeight,
    255                                                          rootBlock, blockX, blockY, tx, ty, paintInfo));
    256 
    257     // When dealing with bidi text, a non-contiguous selection region is possible.
    258     // e.g. The logical text aaaAAAbbb (capitals denote RTL text and non-capitals LTR) is layed out
    259     // visually as 3 text runs |aaa|bbb|AAA| if we select 4 characters from the start of the text the
    260     // selection will look like (underline denotes selection):
    261     // |aaa|bbb|AAA|
    262     //  ___       _
    263     // We can see that the |bbb| run is not part of the selection while the runs around it are.
    264     if (firstBox && firstBox != lastBox) {
    265         // Now fill in any gaps on the line that occurred between two selected elements.
    266         int lastX = firstBox->x() + firstBox->width();
    267         bool isPreviousBoxSelected = firstBox->selectionState() != RenderObject::SelectionNone;
    268         for (InlineBox* box = firstBox->nextLeafChild(); box; box = box->nextLeafChild()) {
    269             if (box->selectionState() != RenderObject::SelectionNone) {
    270                 if (isPreviousBoxSelected)  // VisibleSelection may be non-contiguous, see comment above.
    271                     result.uniteCenter(block()->fillHorizontalSelectionGap(box->parent()->renderer(),
    272                                                                            lastX + tx, selTop + ty,
    273                                                                            box->x() - lastX, selHeight, paintInfo));
    274                 lastX = box->x() + box->width();
    275             }
    276             if (box == lastBox)
    277                 break;
    278             isPreviousBoxSelected = box->selectionState() != RenderObject::SelectionNone;
    279         }
    280     }
    281 
    282     return result;
    283 }
    284 
    285 void RootInlineBox::setHasSelectedChildren(bool b)
    286 {
    287     if (m_hasSelectedChildren == b)
    288         return;
    289     m_hasSelectedChildren = b;
    290 }
    291 
    292 RenderObject::SelectionState RootInlineBox::selectionState()
    293 {
    294     // Walk over all of the selected boxes.
    295     RenderObject::SelectionState state = RenderObject::SelectionNone;
    296     for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) {
    297         RenderObject::SelectionState boxState = box->selectionState();
    298         if ((boxState == RenderObject::SelectionStart && state == RenderObject::SelectionEnd) ||
    299             (boxState == RenderObject::SelectionEnd && state == RenderObject::SelectionStart))
    300             state = RenderObject::SelectionBoth;
    301         else if (state == RenderObject::SelectionNone ||
    302                  ((boxState == RenderObject::SelectionStart || boxState == RenderObject::SelectionEnd) &&
    303                   (state == RenderObject::SelectionNone || state == RenderObject::SelectionInside)))
    304             state = boxState;
    305         if (state == RenderObject::SelectionBoth)
    306             break;
    307     }
    308 
    309     return state;
    310 }
    311 
    312 InlineBox* RootInlineBox::firstSelectedBox()
    313 {
    314     for (InlineBox* box = firstLeafChild(); box; box = box->nextLeafChild()) {
    315         if (box->selectionState() != RenderObject::SelectionNone)
    316             return box;
    317     }
    318 
    319     return 0;
    320 }
    321 
    322 InlineBox* RootInlineBox::lastSelectedBox()
    323 {
    324     for (InlineBox* box = lastLeafChild(); box; box = box->prevLeafChild()) {
    325         if (box->selectionState() != RenderObject::SelectionNone)
    326             return box;
    327     }
    328 
    329     return 0;
    330 }
    331 
    332 int RootInlineBox::selectionTop() const
    333 {
    334     int selectionTop = m_lineTop;
    335     if (!prevRootBox())
    336         return selectionTop;
    337 
    338     int prevBottom = prevRootBox()->selectionBottom();
    339     if (prevBottom < selectionTop && block()->containsFloats()) {
    340         // This line has actually been moved further down, probably from a large line-height, but possibly because the
    341         // line was forced to clear floats.  If so, let's check the offsets, and only be willing to use the previous
    342         // line's bottom overflow if the offsets are greater on both sides.
    343         int prevLeft = block()->leftOffset(prevBottom, !prevRootBox());
    344         int prevRight = block()->rightOffset(prevBottom, !prevRootBox());
    345         int newLeft = block()->leftOffset(selectionTop, !prevRootBox());
    346         int newRight = block()->rightOffset(selectionTop, !prevRootBox());
    347         if (prevLeft > newLeft || prevRight < newRight)
    348             return selectionTop;
    349     }
    350 
    351     return prevBottom;
    352 }
    353 
    354 RenderBlock* RootInlineBox::block() const
    355 {
    356     return toRenderBlock(renderer());
    357 }
    358 
    359 static bool isEditableLeaf(InlineBox* leaf)
    360 {
    361     return leaf && leaf->renderer() && leaf->renderer()->node() && leaf->renderer()->node()->isContentEditable();
    362 }
    363 
    364 InlineBox* RootInlineBox::closestLeafChildForXPos(int x, bool onlyEditableLeaves)
    365 {
    366     InlineBox* firstLeaf = firstLeafChild();
    367     InlineBox* lastLeaf = lastLeafChild();
    368     if (firstLeaf == lastLeaf && (!onlyEditableLeaves || isEditableLeaf(firstLeaf)))
    369         return firstLeaf;
    370 
    371     // Avoid returning a list marker when possible.
    372     if (x <= firstLeaf->m_x && !firstLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(firstLeaf)))
    373         // The x coordinate is less or equal to left edge of the firstLeaf.
    374         // Return it.
    375         return firstLeaf;
    376 
    377     if (x >= lastLeaf->m_x + lastLeaf->m_width && !lastLeaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(lastLeaf)))
    378         // The x coordinate is greater or equal to right edge of the lastLeaf.
    379         // Return it.
    380         return lastLeaf;
    381 
    382     InlineBox* closestLeaf = 0;
    383     for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) {
    384         if (!leaf->renderer()->isListMarker() && (!onlyEditableLeaves || isEditableLeaf(leaf))) {
    385             closestLeaf = leaf;
    386             if (x < leaf->m_x + leaf->m_width)
    387                 // The x coordinate is less than the right edge of the box.
    388                 // Return it.
    389                 return leaf;
    390         }
    391     }
    392 
    393     return closestLeaf ? closestLeaf : lastLeaf;
    394 }
    395 
    396 BidiStatus RootInlineBox::lineBreakBidiStatus() const
    397 {
    398     return BidiStatus(m_lineBreakBidiStatusEor, m_lineBreakBidiStatusLastStrong, m_lineBreakBidiStatusLast, m_lineBreakContext);
    399 }
    400 
    401 void RootInlineBox::setLineBreakInfo(RenderObject* obj, unsigned breakPos, const BidiStatus& status)
    402 {
    403     m_lineBreakObj = obj;
    404     m_lineBreakPos = breakPos;
    405     m_lineBreakBidiStatusEor = status.eor;
    406     m_lineBreakBidiStatusLastStrong = status.lastStrong;
    407     m_lineBreakBidiStatusLast = status.last;
    408     m_lineBreakContext = status.context;
    409 }
    410 
    411 EllipsisBox* RootInlineBox::ellipsisBox() const
    412 {
    413     if (!m_hasEllipsisBox)
    414         return false;
    415     return gEllipsisBoxMap->get(this);
    416 }
    417 
    418 void RootInlineBox::removeLineBoxFromRenderObject()
    419 {
    420     block()->lineBoxes()->removeLineBox(this);
    421 }
    422 
    423 void RootInlineBox::extractLineBoxFromRenderObject()
    424 {
    425     block()->lineBoxes()->extractLineBox(this);
    426 }
    427 
    428 void RootInlineBox::attachLineBoxToRenderObject()
    429 {
    430     block()->lineBoxes()->attachLineBox(this);
    431 }
    432 
    433 } // namespace WebCore
    434