| Index: third_party/WebKit/WebCore/rendering/RenderText.cpp
|
| ===================================================================
|
| --- third_party/WebKit/WebCore/rendering/RenderText.cpp (revision 9310)
|
| +++ third_party/WebKit/WebCore/rendering/RenderText.cpp (working copy)
|
| @@ -1,1202 +1,1302 @@
|
| -/*
|
| - * (C) 1999 Lars Knoll (knoll@kde.org)
|
| - * (C) 2000 Dirk Mueller (mueller@kde.org)
|
| - * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
|
| - * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
|
| - * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com)
|
| - *
|
| - * This library is free software; you can redistribute it and/or
|
| - * modify it under the terms of the GNU Library General Public
|
| - * License as published by the Free Software Foundation; either
|
| - * version 2 of the License, or (at your option) any later version.
|
| - *
|
| - * This library is distributed in the hope that it will be useful,
|
| - * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
| - * Library General Public License for more details.
|
| - *
|
| - * You should have received a copy of the GNU Library General Public License
|
| - * along with this library; see the file COPYING.LIB. If not, write to
|
| - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
| - * Boston, MA 02110-1301, USA.
|
| - *
|
| - */
|
| -
|
| -#include "config.h"
|
| -#include "RenderText.h"
|
| -
|
| -#include "CharacterNames.h"
|
| -#include "FloatQuad.h"
|
| -#include "FrameView.h"
|
| -#include "InlineTextBox.h"
|
| -#include "Range.h"
|
| -#include "RenderArena.h"
|
| -#include "RenderBlock.h"
|
| -#include "RenderLayer.h"
|
| -#include "RenderView.h"
|
| -#include "Text.h"
|
| -#include "TextBreakIterator.h"
|
| -#include "break_lines.h"
|
| -#include <wtf/AlwaysInline.h>
|
| -
|
| -using namespace std;
|
| -using namespace WTF;
|
| -using namespace Unicode;
|
| -
|
| -namespace WebCore {
|
| -
|
| -// FIXME: Move to StringImpl.h eventually.
|
| -static inline bool charactersAreAllASCII(StringImpl* text)
|
| -{
|
| - return charactersAreAllASCII(text->characters(), text->length());
|
| -}
|
| -
|
| -RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str)
|
| - : RenderObject(node)
|
| - , m_text(str)
|
| - , m_firstTextBox(0)
|
| - , m_lastTextBox(0)
|
| - , m_minWidth(-1)
|
| - , m_maxWidth(-1)
|
| - , m_beginMinWidth(0)
|
| - , m_endMinWidth(0)
|
| - , m_hasTab(false)
|
| - , m_linesDirty(false)
|
| - , m_containsReversedText(false)
|
| - , m_isAllASCII(charactersAreAllASCII(m_text.get()))
|
| -{
|
| - ASSERT(m_text);
|
| - setIsText();
|
| - m_text = document()->displayStringModifiedByEncoding(PassRefPtr<StringImpl>(m_text));
|
| -
|
| - view()->frameView()->setIsVisuallyNonEmpty();
|
| -}
|
| -
|
| -#ifndef NDEBUG
|
| -
|
| -RenderText::~RenderText()
|
| -{
|
| - ASSERT(!m_firstTextBox);
|
| - ASSERT(!m_lastTextBox);
|
| -}
|
| -
|
| -#endif
|
| -
|
| -const char* RenderText::renderName() const
|
| -{
|
| - return "RenderText";
|
| -}
|
| -
|
| -bool RenderText::isTextFragment() const
|
| -{
|
| - return false;
|
| -}
|
| -
|
| -bool RenderText::isWordBreak() const
|
| -{
|
| - return false;
|
| -}
|
| -
|
| -void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
|
| -{
|
| - // There is no need to ever schedule repaints from a style change of a text run, since
|
| - // we already did this for the parent of the text run.
|
| - // We do have to schedule layouts, though, since a style change can force us to
|
| - // need to relayout.
|
| - if (diff == StyleDifferenceLayout)
|
| - setNeedsLayoutAndPrefWidthsRecalc();
|
| -
|
| - ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE;
|
| - ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE;
|
| -
|
| - if (oldTransform != style()->textTransform() || oldSecurity != style()->textSecurity()) {
|
| - if (RefPtr<StringImpl> textToTransform = originalText())
|
| - setText(textToTransform.release(), true);
|
| - }
|
| -}
|
| -
|
| -void RenderText::destroy()
|
| -{
|
| - if (!documentBeingDestroyed()) {
|
| - if (firstTextBox()) {
|
| - if (isBR()) {
|
| - RootInlineBox* next = firstTextBox()->root()->nextRootBox();
|
| - if (next)
|
| - next->markDirty();
|
| - }
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - box->remove();
|
| - } else if (parent())
|
| - parent()->dirtyLinesFromChangedChild(this);
|
| - }
|
| - deleteTextBoxes();
|
| - RenderObject::destroy();
|
| -}
|
| -
|
| -void RenderText::extractTextBox(InlineTextBox* box)
|
| -{
|
| - checkConsistency();
|
| -
|
| - m_lastTextBox = box->prevTextBox();
|
| - if (box == m_firstTextBox)
|
| - m_firstTextBox = 0;
|
| - if (box->prevTextBox())
|
| - box->prevTextBox()->setNextLineBox(0);
|
| - box->setPreviousLineBox(0);
|
| - for (InlineRunBox* curr = box; curr; curr = curr->nextLineBox())
|
| - curr->setExtracted();
|
| -
|
| - checkConsistency();
|
| -}
|
| -
|
| -void RenderText::attachTextBox(InlineTextBox* box)
|
| -{
|
| - checkConsistency();
|
| -
|
| - if (m_lastTextBox) {
|
| - m_lastTextBox->setNextLineBox(box);
|
| - box->setPreviousLineBox(m_lastTextBox);
|
| - } else
|
| - m_firstTextBox = box;
|
| - InlineTextBox* last = box;
|
| - for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) {
|
| - curr->setExtracted(false);
|
| - last = curr;
|
| - }
|
| - m_lastTextBox = last;
|
| -
|
| - checkConsistency();
|
| -}
|
| -
|
| -void RenderText::removeTextBox(InlineTextBox* box)
|
| -{
|
| - checkConsistency();
|
| -
|
| - if (box == m_firstTextBox)
|
| - m_firstTextBox = box->nextTextBox();
|
| - if (box == m_lastTextBox)
|
| - m_lastTextBox = box->prevTextBox();
|
| - if (box->nextTextBox())
|
| - box->nextTextBox()->setPreviousLineBox(box->prevTextBox());
|
| - if (box->prevTextBox())
|
| - box->prevTextBox()->setNextLineBox(box->nextTextBox());
|
| -
|
| - checkConsistency();
|
| -}
|
| -
|
| -void RenderText::deleteTextBoxes()
|
| -{
|
| - if (firstTextBox()) {
|
| - RenderArena* arena = renderArena();
|
| - InlineTextBox* next;
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = next) {
|
| - next = curr->nextTextBox();
|
| - curr->destroy(arena);
|
| - }
|
| - m_firstTextBox = m_lastTextBox = 0;
|
| - }
|
| -}
|
| -
|
| -PassRefPtr<StringImpl> RenderText::originalText() const
|
| -{
|
| - Node* e = element();
|
| - return e ? static_cast<Text*>(e)->string() : 0;
|
| -}
|
| -
|
| -void RenderText::absoluteRects(Vector<IntRect>& rects, int tx, int ty, bool)
|
| -{
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - rects.append(IntRect(tx + box->xPos(), ty + box->yPos(), box->width(), box->height()));
|
| -}
|
| -
|
| -void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight)
|
| -{
|
| - // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
|
| - // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
|
| - // function to take ints causes various internal mismatches. But selectionRect takes ints, and
|
| - // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
|
| - // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
|
| - ASSERT(end == UINT_MAX || end <= INT_MAX);
|
| - ASSERT(start <= INT_MAX);
|
| - start = min(start, static_cast<unsigned>(INT_MAX));
|
| - end = min(end, static_cast<unsigned>(INT_MAX));
|
| -
|
| - FloatPoint absPos = localToAbsolute(FloatPoint());
|
| -
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - // Note: box->end() returns the index of the last character, not the index past it
|
| - if (start <= box->start() && box->end() < end) {
|
| - IntRect r = IntRect(absPos.x() + box->xPos(), absPos.y() + box->yPos(), box->width(), box->height());
|
| - if (useSelectionHeight) {
|
| - IntRect selectionRect = box->selectionRect(absPos.x(), absPos.y(), start, end);
|
| - r.setHeight(selectionRect.height());
|
| - r.setY(selectionRect.y());
|
| - }
|
| - rects.append(r);
|
| - } else {
|
| - unsigned realEnd = min(box->end() + 1, end);
|
| - IntRect r = box->selectionRect(absPos.x(), absPos.y(), start, realEnd);
|
| - if (!r.isEmpty()) {
|
| - if (!useSelectionHeight) {
|
| - // change the height and y position because selectionRect uses selection-specific values
|
| - r.setHeight(box->height());
|
| - r.setY(absPos.y() + box->yPos());
|
| - }
|
| - rects.append(r);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool)
|
| -{
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - quads.append(localToAbsoluteQuad(FloatRect(box->xPos(), box->yPos(), box->width(), box->height())));
|
| -}
|
| -
|
| -void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight)
|
| -{
|
| - // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
|
| - // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
|
| - // function to take ints causes various internal mismatches. But selectionRect takes ints, and
|
| - // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
|
| - // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
|
| - ASSERT(end == UINT_MAX || end <= INT_MAX);
|
| - ASSERT(start <= INT_MAX);
|
| - start = min(start, static_cast<unsigned>(INT_MAX));
|
| - end = min(end, static_cast<unsigned>(INT_MAX));
|
| -
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - // Note: box->end() returns the index of the last character, not the index past it
|
| - if (start <= box->start() && box->end() < end) {
|
| - IntRect r = IntRect(box->xPos(), box->yPos(), box->width(), box->height());
|
| - if (useSelectionHeight) {
|
| - IntRect selectionRect = box->selectionRect(0, 0, start, end);
|
| - r.setHeight(selectionRect.height());
|
| - r.setY(selectionRect.y());
|
| - }
|
| - quads.append(localToAbsoluteQuad(FloatRect(r)));
|
| - } else {
|
| - unsigned realEnd = min(box->end() + 1, end);
|
| - IntRect r = box->selectionRect(0, 0, start, realEnd);
|
| - if (!r.isEmpty()) {
|
| - if (!useSelectionHeight) {
|
| - // change the height and y position because selectionRect uses selection-specific values
|
| - r.setHeight(box->height());
|
| - r.setY(box->yPos());
|
| - }
|
| - quads.append(localToAbsoluteQuad(FloatRect(r)));
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -InlineTextBox* RenderText::findNextInlineTextBox(int offset, int& pos) const
|
| -{
|
| - // The text runs point to parts of the RenderText's m_text
|
| - // (they don't include '\n')
|
| - // Find the text run that includes the character at offset
|
| - // and return pos, which is the position of the char in the run.
|
| -
|
| - if (!m_firstTextBox)
|
| - return 0;
|
| -
|
| - InlineTextBox* s = m_firstTextBox;
|
| - int off = s->len();
|
| - while (offset > off && s->nextTextBox()) {
|
| - s = s->nextTextBox();
|
| - off = s->start() + s->len();
|
| - }
|
| - // we are now in the correct text run
|
| - pos = (offset > off ? s->len() : s->len() - (off - offset) );
|
| - return s;
|
| -}
|
| -
|
| -VisiblePosition RenderText::positionForCoordinates(int x, int y)
|
| -{
|
| - if (!firstTextBox() || textLength() == 0)
|
| - return VisiblePosition(element(), 0, DOWNSTREAM);
|
| -
|
| - // Get the offset for the position, since this will take rtl text into account.
|
| - int offset;
|
| -
|
| - // FIXME: We should be able to roll these special cases into the general cases in the loop below.
|
| - if (firstTextBox() && y < firstTextBox()->root()->bottomOverflow() && x < firstTextBox()->m_x) {
|
| - // at the y coordinate of the first line or above
|
| - // and the x coordinate is to the left of the first text box left edge
|
| - offset = firstTextBox()->offsetForPosition(x);
|
| - return VisiblePosition(element(), offset + firstTextBox()->start(), DOWNSTREAM);
|
| - }
|
| - if (lastTextBox() && y >= lastTextBox()->root()->topOverflow() && x >= lastTextBox()->m_x + lastTextBox()->m_width) {
|
| - // at the y coordinate of the last line or below
|
| - // and the x coordinate is to the right of the last text box right edge
|
| - offset = lastTextBox()->offsetForPosition(x);
|
| - return VisiblePosition(element(), offset + lastTextBox()->start(), DOWNSTREAM);
|
| - }
|
| -
|
| - InlineTextBox* lastBoxAbove = 0;
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - if (y >= box->root()->topOverflow()) {
|
| - int bottom = box->root()->nextRootBox() ? box->root()->nextRootBox()->topOverflow() : box->root()->bottomOverflow();
|
| - if (y < bottom) {
|
| - offset = box->offsetForPosition(x);
|
| -
|
| - if (x == box->m_x)
|
| - // the x coordinate is equal to the left edge of this box
|
| - // the affinity must be downstream so the position doesn't jump back to the previous line
|
| - return VisiblePosition(element(), offset + box->start(), DOWNSTREAM);
|
| -
|
| - if (x < box->m_x + box->m_width)
|
| - // and the x coordinate is to the left of the right edge of this box
|
| - // check to see if position goes in this box
|
| - return VisiblePosition(element(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
|
| -
|
| - if (!box->prevOnLine() && x < box->m_x)
|
| - // box is first on line
|
| - // and the x coordinate is to the left of the first text box left edge
|
| - return VisiblePosition(element(), offset + box->start(), DOWNSTREAM);
|
| -
|
| - if (!box->nextOnLine())
|
| - // box is last on line
|
| - // and the x coordinate is to the right of the last text box right edge
|
| - // generate VisiblePosition, use UPSTREAM affinity if possible
|
| - return VisiblePosition(element(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
|
| - }
|
| - lastBoxAbove = box;
|
| - }
|
| - }
|
| -
|
| - return VisiblePosition(element(), lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM);
|
| -}
|
| -
|
| -IntRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine)
|
| -{
|
| - if (!inlineBox)
|
| - return IntRect();
|
| -
|
| - ASSERT(inlineBox->isInlineTextBox());
|
| - if (!inlineBox->isInlineTextBox())
|
| - return IntRect();
|
| -
|
| - InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox);
|
| -
|
| - int height = box->root()->bottomOverflow() - box->root()->topOverflow();
|
| - int top = box->root()->topOverflow();
|
| -
|
| - int left = box->positionForOffset(caretOffset);
|
| -
|
| - int rootLeft = box->root()->xPos();
|
| - // FIXME: should we use the width of the root inline box or the
|
| - // width of the containing block for this?
|
| - if (extraWidthToEndOfLine)
|
| - *extraWidthToEndOfLine = (box->root()->width() + rootLeft) - (left + 1);
|
| -
|
| - RenderBlock* cb = containingBlock();
|
| - if (style()->autoWrap()) {
|
| - int availableWidth = cb->lineWidth(top, false);
|
| - if (box->direction() == LTR)
|
| - left = min(left, rootLeft + availableWidth - 1);
|
| - else
|
| - left = max(left, rootLeft);
|
| - }
|
| -
|
| - const int caretWidth = 1;
|
| - return IntRect(left, top, caretWidth, height);
|
| -}
|
| -
|
| -ALWAYS_INLINE int RenderText::widthFromCache(const Font& f, int start, int len, int xPos) const
|
| -{
|
| - if (f.isFixedPitch() && !f.isSmallCaps() && m_isAllASCII) {
|
| - int monospaceCharacterWidth = f.spaceWidth();
|
| - int tabWidth = allowTabs() ? monospaceCharacterWidth * 8 : 0;
|
| - int w = 0;
|
| - bool isSpace;
|
| - bool previousCharWasSpace = true; // FIXME: Preserves historical behavior, but seems wrong for start > 0.
|
| - for (int i = start; i < start + len; i++) {
|
| - char c = (*m_text)[i];
|
| - if (c <= ' ') {
|
| - if (c == ' ' || c == '\n') {
|
| - w += monospaceCharacterWidth;
|
| - isSpace = true;
|
| - } else if (c == '\t') {
|
| - w += tabWidth ? tabWidth - ((xPos + w) % tabWidth) : monospaceCharacterWidth;
|
| - isSpace = true;
|
| - } else
|
| - isSpace = false;
|
| - } else {
|
| - w += monospaceCharacterWidth;
|
| - isSpace = false;
|
| - }
|
| - if (isSpace && !previousCharWasSpace)
|
| - w += f.wordSpacing();
|
| - previousCharWasSpace = isSpace;
|
| - }
|
| - return w;
|
| - }
|
| -
|
| - return f.width(TextRun(text()->characters() + start, len, allowTabs(), xPos));
|
| -}
|
| -
|
| -void RenderText::trimmedPrefWidths(int leadWidth,
|
| - int& beginMinW, bool& beginWS,
|
| - int& endMinW, bool& endWS,
|
| - bool& hasBreakableChar, bool& hasBreak,
|
| - int& beginMaxW, int& endMaxW,
|
| - int& minW, int& maxW, bool& stripFrontSpaces)
|
| -{
|
| - bool collapseWhiteSpace = style()->collapseWhiteSpace();
|
| - if (!collapseWhiteSpace)
|
| - stripFrontSpaces = false;
|
| -
|
| - if (m_hasTab || prefWidthsDirty())
|
| - calcPrefWidths(leadWidth);
|
| -
|
| - beginWS = !stripFrontSpaces && m_hasBeginWS;
|
| - endWS = m_hasEndWS;
|
| -
|
| - int len = textLength();
|
| -
|
| - if (!len || (stripFrontSpaces && m_text->containsOnlyWhitespace())) {
|
| - beginMinW = 0;
|
| - endMinW = 0;
|
| - beginMaxW = 0;
|
| - endMaxW = 0;
|
| - minW = 0;
|
| - maxW = 0;
|
| - hasBreak = false;
|
| - return;
|
| - }
|
| -
|
| - minW = m_minWidth;
|
| - maxW = m_maxWidth;
|
| -
|
| - beginMinW = m_beginMinWidth;
|
| - endMinW = m_endMinWidth;
|
| -
|
| - hasBreakableChar = m_hasBreakableChar;
|
| - hasBreak = m_hasBreak;
|
| -
|
| - if ((*m_text)[0] == ' ' || ((*m_text)[0] == '\n' && !style()->preserveNewline()) || (*m_text)[0] == '\t') {
|
| - const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| - if (stripFrontSpaces) {
|
| - const UChar space = ' ';
|
| - int spaceWidth = f.width(TextRun(&space, 1));
|
| - maxW -= spaceWidth;
|
| - } else
|
| - maxW += f.wordSpacing();
|
| - }
|
| -
|
| - stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
|
| -
|
| - if (!style()->autoWrap() || minW > maxW)
|
| - minW = maxW;
|
| -
|
| - // Compute our max widths by scanning the string for newlines.
|
| - if (hasBreak) {
|
| - const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| - bool firstLine = true;
|
| - beginMaxW = maxW;
|
| - endMaxW = maxW;
|
| - for (int i = 0; i < len; i++) {
|
| - int linelen = 0;
|
| - while (i + linelen < len && (*m_text)[i + linelen] != '\n')
|
| - linelen++;
|
| -
|
| - if (linelen) {
|
| - endMaxW = widthFromCache(f, i, linelen, leadWidth + endMaxW);
|
| - if (firstLine) {
|
| - firstLine = false;
|
| - leadWidth = 0;
|
| - beginMaxW = endMaxW;
|
| - }
|
| - i += linelen;
|
| - } else if (firstLine) {
|
| - beginMaxW = 0;
|
| - firstLine = false;
|
| - leadWidth = 0;
|
| - }
|
| -
|
| - if (i == len - 1)
|
| - // A <pre> run that ends with a newline, as in, e.g.,
|
| - // <pre>Some text\n\n<span>More text</pre>
|
| - endMaxW = 0;
|
| - }
|
| - }
|
| -}
|
| -
|
| -static inline bool isSpaceAccordingToStyle(UChar c, RenderStyle* style)
|
| -{
|
| - return c == ' ' || (c == noBreakSpace && style->nbspMode() == SPACE);
|
| -}
|
| -
|
| -int RenderText::minPrefWidth() const
|
| -{
|
| - if (prefWidthsDirty())
|
| - const_cast<RenderText*>(this)->calcPrefWidths(0);
|
| -
|
| - return m_minWidth;
|
| -}
|
| -
|
| -int RenderText::maxPrefWidth() const
|
| -{
|
| - if (prefWidthsDirty())
|
| - const_cast<RenderText*>(this)->calcPrefWidths(0);
|
| -
|
| - return m_maxWidth;
|
| -}
|
| -
|
| -void RenderText::calcPrefWidths(int leadWidth)
|
| -{
|
| - ASSERT(m_hasTab || prefWidthsDirty());
|
| -
|
| - m_minWidth = 0;
|
| - m_beginMinWidth = 0;
|
| - m_endMinWidth = 0;
|
| - m_maxWidth = 0;
|
| -
|
| - if (isBR())
|
| - return;
|
| -
|
| - int currMinWidth = 0;
|
| - int currMaxWidth = 0;
|
| - m_hasBreakableChar = false;
|
| - m_hasBreak = false;
|
| - m_hasTab = false;
|
| - m_hasBeginWS = false;
|
| - m_hasEndWS = false;
|
| -
|
| - const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| - int wordSpacing = style()->wordSpacing();
|
| - int len = textLength();
|
| - const UChar* txt = characters();
|
| - bool needsWordSpacing = false;
|
| - bool ignoringSpaces = false;
|
| - bool isSpace = false;
|
| - bool firstWord = true;
|
| - bool firstLine = true;
|
| - int nextBreakable = -1;
|
| - int lastWordBoundary = 0;
|
| -
|
| - bool breakNBSP = style()->autoWrap() && style()->nbspMode() == SPACE;
|
| - bool breakAll = (style()->wordBreak() == BreakAllWordBreak || style()->wordBreak() == BreakWordBreak) && style()->autoWrap();
|
| -
|
| - for (int i = 0; i < len; i++) {
|
| - UChar c = txt[i];
|
| -
|
| - bool previousCharacterIsSpace = isSpace;
|
| -
|
| - bool isNewline = false;
|
| - if (c == '\n') {
|
| - if (style()->preserveNewline()) {
|
| - m_hasBreak = true;
|
| - isNewline = true;
|
| - isSpace = false;
|
| - } else
|
| - isSpace = true;
|
| - } else if (c == '\t') {
|
| - if (!style()->collapseWhiteSpace()) {
|
| - m_hasTab = true;
|
| - isSpace = false;
|
| - } else
|
| - isSpace = true;
|
| - } else
|
| - isSpace = c == ' ';
|
| -
|
| - if ((isSpace || isNewline) && !i)
|
| - m_hasBeginWS = true;
|
| - if ((isSpace || isNewline) && i == len - 1)
|
| - m_hasEndWS = true;
|
| -
|
| - if (!ignoringSpaces && style()->collapseWhiteSpace() && previousCharacterIsSpace && isSpace)
|
| - ignoringSpaces = true;
|
| -
|
| - if (ignoringSpaces && !isSpace)
|
| - ignoringSpaces = false;
|
| -
|
| - // Ignore spaces and soft hyphens
|
| - if (ignoringSpaces) {
|
| - ASSERT(lastWordBoundary == i);
|
| - lastWordBoundary++;
|
| - continue;
|
| - } else if (c == softHyphen) {
|
| - currMaxWidth += widthFromCache(f, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth);
|
| - lastWordBoundary = i + 1;
|
| - continue;
|
| - }
|
| -
|
| - bool hasBreak = breakAll || isBreakable(txt, i, len, nextBreakable, breakNBSP);
|
| - bool betweenWords = true;
|
| - int j = i;
|
| - while (c != '\n' && !isSpaceAccordingToStyle(c, style()) && c != '\t' && c != softHyphen) {
|
| - j++;
|
| - if (j == len)
|
| - break;
|
| - c = txt[j];
|
| - if (isBreakable(txt, j, len, nextBreakable, breakNBSP))
|
| - break;
|
| - if (breakAll) {
|
| - betweenWords = false;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - int wordLen = j - i;
|
| - if (wordLen) {
|
| - int w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth);
|
| - currMinWidth += w;
|
| - if (betweenWords) {
|
| - if (lastWordBoundary == i)
|
| - currMaxWidth += w;
|
| - else
|
| - currMaxWidth += widthFromCache(f, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth);
|
| - lastWordBoundary = j;
|
| - }
|
| -
|
| - bool isSpace = (j < len) && isSpaceAccordingToStyle(c, style());
|
| - bool isCollapsibleWhiteSpace = (j < len) && style()->isCollapsibleWhiteSpace(c);
|
| - if (j < len && style()->autoWrap())
|
| - m_hasBreakableChar = true;
|
| -
|
| - // Add in wordSpacing to our currMaxWidth, but not if this is the last word on a line or the
|
| - // last word in the run.
|
| - if (wordSpacing && (isSpace || isCollapsibleWhiteSpace) && !containsOnlyWhitespace(j, len-j))
|
| - currMaxWidth += wordSpacing;
|
| -
|
| - if (firstWord) {
|
| - firstWord = false;
|
| - // If the first character in the run is breakable, then we consider ourselves to have a beginning
|
| - // minimum width of 0, since a break could occur right before our run starts, preventing us from ever
|
| - // being appended to a previous text run when considering the total minimum width of the containing block.
|
| - if (hasBreak)
|
| - m_hasBreakableChar = true;
|
| - m_beginMinWidth = hasBreak ? 0 : w;
|
| - }
|
| - m_endMinWidth = w;
|
| -
|
| - if (currMinWidth > m_minWidth)
|
| - m_minWidth = currMinWidth;
|
| - currMinWidth = 0;
|
| -
|
| - i += wordLen - 1;
|
| - } else {
|
| - // Nowrap can never be broken, so don't bother setting the
|
| - // breakable character boolean. Pre can only be broken if we encounter a newline.
|
| - if (style()->autoWrap() || isNewline)
|
| - m_hasBreakableChar = true;
|
| -
|
| - if (currMinWidth > m_minWidth)
|
| - m_minWidth = currMinWidth;
|
| - currMinWidth = 0;
|
| -
|
| - if (isNewline) { // Only set if preserveNewline was true and we saw a newline.
|
| - if (firstLine) {
|
| - firstLine = false;
|
| - leadWidth = 0;
|
| - if (!style()->autoWrap())
|
| - m_beginMinWidth = currMaxWidth;
|
| - }
|
| -
|
| - if (currMaxWidth > m_maxWidth)
|
| - m_maxWidth = currMaxWidth;
|
| - currMaxWidth = 0;
|
| - } else {
|
| - currMaxWidth += f.width(TextRun(txt + i, 1, allowTabs(), leadWidth + currMaxWidth));
|
| - needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len - 1;
|
| - }
|
| - ASSERT(lastWordBoundary == i);
|
| - lastWordBoundary++;
|
| - }
|
| - }
|
| -
|
| - if (needsWordSpacing && len > 1 || ignoringSpaces && !firstWord)
|
| - currMaxWidth += wordSpacing;
|
| -
|
| - m_minWidth = max(currMinWidth, m_minWidth);
|
| - m_maxWidth = max(currMaxWidth, m_maxWidth);
|
| -
|
| - if (!style()->autoWrap())
|
| - m_minWidth = m_maxWidth;
|
| -
|
| - if (style()->whiteSpace() == PRE) {
|
| - if (firstLine)
|
| - m_beginMinWidth = m_maxWidth;
|
| - m_endMinWidth = currMaxWidth;
|
| - }
|
| -
|
| - setPrefWidthsDirty(false);
|
| -}
|
| -
|
| -bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
|
| -{
|
| - unsigned currPos;
|
| - for (currPos = from;
|
| - currPos < from + len && ((*m_text)[currPos] == '\n' || (*m_text)[currPos] == ' ' || (*m_text)[currPos] == '\t');
|
| - currPos++) { }
|
| - return currPos >= (from + len);
|
| -}
|
| -
|
| -int RenderText::firstRunX() const
|
| -{
|
| - return m_firstTextBox ? m_firstTextBox->m_x : 0;
|
| -}
|
| -
|
| -int RenderText::firstRunY() const
|
| -{
|
| - return m_firstTextBox ? m_firstTextBox->m_y : 0;
|
| -}
|
| -
|
| -void RenderText::setSelectionState(SelectionState state)
|
| -{
|
| - InlineTextBox* box;
|
| -
|
| - RenderObject::setSelectionState(state);
|
| - if (state == SelectionStart || state == SelectionEnd || state == SelectionBoth) {
|
| - int startPos, endPos;
|
| - selectionStartEnd(startPos, endPos);
|
| - if (selectionState() == SelectionStart) {
|
| - endPos = textLength();
|
| -
|
| - // to handle selection from end of text to end of line
|
| - if (startPos != 0 && startPos == endPos)
|
| - startPos = endPos - 1;
|
| - } else if (selectionState() == SelectionEnd)
|
| - startPos = 0;
|
| -
|
| - for (box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - if (box->isSelected(startPos, endPos)) {
|
| - RootInlineBox* line = box->root();
|
| - if (line)
|
| - line->setHasSelectedChildren(true);
|
| - }
|
| - }
|
| - } else {
|
| - for (box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - RootInlineBox* line = box->root();
|
| - if (line)
|
| - line->setHasSelectedChildren(state == SelectionInside);
|
| - }
|
| - }
|
| -
|
| - containingBlock()->setSelectionState(state);
|
| -}
|
| -
|
| -void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset, unsigned len, bool force)
|
| -{
|
| - unsigned oldLen = textLength();
|
| - unsigned newLen = text->length();
|
| - int delta = newLen - oldLen;
|
| - unsigned end = len ? offset + len - 1 : offset;
|
| -
|
| - RootInlineBox* firstRootBox = 0;
|
| - RootInlineBox* lastRootBox = 0;
|
| -
|
| - bool dirtiedLines = false;
|
| -
|
| - // Dirty all text boxes that include characters in between offset and offset+len.
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| - // Text run is entirely before the affected range.
|
| - if (curr->end() < offset)
|
| - continue;
|
| -
|
| - // Text run is entirely after the affected range.
|
| - if (curr->start() > end) {
|
| - curr->offsetRun(delta);
|
| - RootInlineBox* root = curr->root();
|
| - if (!firstRootBox) {
|
| - firstRootBox = root;
|
| - if (!dirtiedLines) {
|
| - // The affected area was in between two runs. Go ahead and mark the root box of
|
| - // the run after the affected area as dirty.
|
| - firstRootBox->markDirty();
|
| - dirtiedLines = true;
|
| - }
|
| - }
|
| - lastRootBox = root;
|
| - } else if (curr->end() >= offset && curr->end() <= end) {
|
| - // Text run overlaps with the left end of the affected range.
|
| - curr->dirtyLineBoxes();
|
| - dirtiedLines = true;
|
| - } else if (curr->start() <= offset && curr->end() >= end) {
|
| - // Text run subsumes the affected range.
|
| - curr->dirtyLineBoxes();
|
| - dirtiedLines = true;
|
| - } else if (curr->start() <= end && curr->end() >= end) {
|
| - // Text run overlaps with right end of the affected range.
|
| - curr->dirtyLineBoxes();
|
| - dirtiedLines = true;
|
| - }
|
| - }
|
| -
|
| - // Now we have to walk all of the clean lines and adjust their cached line break information
|
| - // to reflect our updated offsets.
|
| - if (lastRootBox)
|
| - lastRootBox = lastRootBox->nextRootBox();
|
| - if (firstRootBox) {
|
| - RootInlineBox* prev = firstRootBox->prevRootBox();
|
| - if (prev)
|
| - firstRootBox = prev;
|
| - }
|
| - for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) {
|
| - if (curr->lineBreakObj() == this && curr->lineBreakPos() > end)
|
| - curr->setLineBreakPos(curr->lineBreakPos() + delta);
|
| - }
|
| -
|
| - // If the text node is empty, dirty the line where new text will be inserted.
|
| - if (!firstTextBox() && parent()) {
|
| - parent()->dirtyLinesFromChangedChild(this);
|
| - dirtiedLines = true;
|
| - }
|
| -
|
| - m_linesDirty = dirtiedLines;
|
| - setText(text, force);
|
| -}
|
| -
|
| -static inline bool isInlineFlowOrEmptyText(RenderObject* o)
|
| -{
|
| - if (o->isRenderInline())
|
| - return true;
|
| - if (!o->isText())
|
| - return false;
|
| - StringImpl* text = toRenderText(o)->text();
|
| - if (!text)
|
| - return true;
|
| - return !text->length();
|
| -}
|
| -
|
| -UChar RenderText::previousCharacter()
|
| -{
|
| - // find previous text renderer if one exists
|
| - RenderObject* previousText = this;
|
| - while ((previousText = previousText->previousInPreOrder()))
|
| - if (!isInlineFlowOrEmptyText(previousText))
|
| - break;
|
| - UChar prev = ' ';
|
| - if (previousText && previousText->isText())
|
| - if (StringImpl* previousString = toRenderText(previousText)->text())
|
| - prev = (*previousString)[previousString->length() - 1];
|
| - return prev;
|
| -}
|
| -
|
| -void RenderText::setTextInternal(PassRefPtr<StringImpl> text)
|
| -{
|
| - m_text = text;
|
| - ASSERT(m_text);
|
| -
|
| - m_text = document()->displayStringModifiedByEncoding(PassRefPtr<StringImpl>(m_text));
|
| -#if ENABLE(SVG)
|
| - if (isSVGText()) {
|
| - if (style() && style()->whiteSpace() == PRE) {
|
| - // Spec: When xml:space="preserve", the SVG user agent will do the following using a
|
| - // copy of the original character data content. It will convert all newline and tab
|
| - // characters into space characters. Then, it will draw all space characters, including
|
| - // leading, trailing and multiple contiguous space characters.
|
| -
|
| - m_text = m_text->replace('\n', ' ');
|
| -
|
| - // If xml:space="preserve" is set, white-space is set to "pre", which
|
| - // preserves leading, trailing & contiguous space character for us.
|
| - } else {
|
| - // Spec: When xml:space="default", the SVG user agent will do the following using a
|
| - // copy of the original character data content. First, it will remove all newline
|
| - // characters. Then it will convert all tab characters into space characters.
|
| - // Then, it will strip off all leading and trailing space characters.
|
| - // Then, all contiguous space characters will be consolidated.
|
| -
|
| - m_text = m_text->replace('\n', StringImpl::empty());
|
| -
|
| - // If xml:space="default" is set, white-space is set to "nowrap", which handles
|
| - // leading, trailing & contiguous space character removal for us.
|
| - }
|
| -
|
| - m_text = m_text->replace('\t', ' ');
|
| - }
|
| -#endif
|
| -
|
| - if (style()) {
|
| - switch (style()->textTransform()) {
|
| - case TTNONE:
|
| - break;
|
| - case CAPITALIZE: {
|
| - m_text = m_text->capitalize(previousCharacter());
|
| - break;
|
| - }
|
| - case UPPERCASE:
|
| - m_text = m_text->upper();
|
| - break;
|
| - case LOWERCASE:
|
| - m_text = m_text->lower();
|
| - break;
|
| - }
|
| -
|
| - // We use the same characters here as for list markers.
|
| - // See the listMarkerText function in RenderListMarker.cpp.
|
| - switch (style()->textSecurity()) {
|
| - case TSNONE:
|
| - break;
|
| - case TSCIRCLE:
|
| - m_text = m_text->secure(whiteBullet);
|
| - break;
|
| - case TSDISC:
|
| - m_text = m_text->secure(bullet);
|
| - break;
|
| - case TSSQUARE:
|
| - m_text = m_text->secure(blackSquare);
|
| - }
|
| - }
|
| -
|
| - ASSERT(m_text);
|
| - ASSERT(!isBR() || (textLength() == 1 && (*m_text)[0] == '\n'));
|
| -
|
| - m_isAllASCII = charactersAreAllASCII(m_text.get());
|
| -}
|
| -
|
| -void RenderText::setText(PassRefPtr<StringImpl> text, bool force)
|
| -{
|
| - ASSERT(text);
|
| -
|
| - if (!force && equal(m_text.get(), text.get()))
|
| - return;
|
| -
|
| - setTextInternal(text);
|
| - setNeedsLayoutAndPrefWidthsRecalc();
|
| -}
|
| -
|
| -int RenderText::lineHeight(bool firstLine, bool) const
|
| -{
|
| - // Always use the interior line height of the parent (e.g., if our parent is an inline block).
|
| - return parent()->lineHeight(firstLine, true);
|
| -}
|
| -
|
| -void RenderText::dirtyLineBoxes(bool fullLayout, bool)
|
| -{
|
| - if (fullLayout)
|
| - deleteTextBoxes();
|
| - else if (!m_linesDirty) {
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - box->dirtyLineBoxes();
|
| - }
|
| - m_linesDirty = false;
|
| -}
|
| -
|
| -InlineTextBox* RenderText::createInlineTextBox()
|
| -{
|
| - return new (renderArena()) InlineTextBox(this);
|
| -}
|
| -
|
| -InlineBox* RenderText::createInlineBox(bool, bool unusedIsRootLineBox, bool)
|
| -{
|
| - ASSERT_UNUSED(unusedIsRootLineBox, !unusedIsRootLineBox);
|
| -
|
| - InlineTextBox* textBox = createInlineTextBox();
|
| - if (!m_firstTextBox)
|
| - m_firstTextBox = m_lastTextBox = textBox;
|
| - else {
|
| - m_lastTextBox->setNextLineBox(textBox);
|
| - textBox->setPreviousLineBox(m_lastTextBox);
|
| - m_lastTextBox = textBox;
|
| - }
|
| - return textBox;
|
| -}
|
| -
|
| -void RenderText::position(InlineBox* box)
|
| -{
|
| - InlineTextBox* s = static_cast<InlineTextBox*>(box);
|
| -
|
| - // FIXME: should not be needed!!!
|
| - if (!s->len()) {
|
| - // We want the box to be destroyed.
|
| - s->remove();
|
| - s->destroy(renderArena());
|
| - m_firstTextBox = m_lastTextBox = 0;
|
| - return;
|
| - }
|
| -
|
| - m_containsReversedText |= s->direction() == RTL;
|
| -}
|
| -
|
| -unsigned RenderText::width(unsigned from, unsigned len, int xPos, bool firstLine) const
|
| -{
|
| - if (from >= textLength())
|
| - return 0;
|
| -
|
| - if (from + len > textLength())
|
| - len = textLength() - from;
|
| -
|
| - return width(from, len, style(firstLine)->font(), xPos);
|
| -}
|
| -
|
| -unsigned RenderText::width(unsigned from, unsigned len, const Font& f, int xPos) const
|
| -{
|
| - ASSERT(from + len <= textLength());
|
| - if (!characters())
|
| - return 0;
|
| -
|
| - int w;
|
| - if (&f == &style()->font()) {
|
| - if (!style()->preserveNewline() && !from && len == textLength())
|
| - w = maxPrefWidth();
|
| - else
|
| - w = widthFromCache(f, from, len, xPos);
|
| - } else
|
| - w = f.width(TextRun(text()->characters() + from, len, allowTabs(), xPos));
|
| -
|
| - return w;
|
| -}
|
| -
|
| -IntRect RenderText::linesBoundingBox() const
|
| -{
|
| - IntRect result;
|
| -
|
| - ASSERT(!firstTextBox() == !lastTextBox()); // Either both are null or both exist.
|
| - if (firstTextBox() && lastTextBox()) {
|
| - // Return the width of the minimal left side and the maximal right side.
|
| - int leftSide = 0;
|
| - int rightSide = 0;
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| - if (curr == firstTextBox() || curr->xPos() < leftSide)
|
| - leftSide = curr->xPos();
|
| - if (curr == firstTextBox() || curr->xPos() + curr->width() > rightSide)
|
| - rightSide = curr->xPos() + curr->width();
|
| - }
|
| - result.setWidth(rightSide - leftSide);
|
| - result.setX(leftSide);
|
| - result.setHeight(lastTextBox()->yPos() + lastTextBox()->height() - firstTextBox()->yPos());
|
| - result.setY(firstTextBox()->yPos());
|
| - }
|
| -
|
| - return result;
|
| -}
|
| -
|
| -IntRect RenderText::clippedOverflowRectForRepaint(RenderBox* repaintContainer)
|
| -{
|
| - RenderObject* cb = containingBlock();
|
| - return cb->clippedOverflowRectForRepaint(repaintContainer);
|
| -}
|
| -
|
| -IntRect RenderText::selectionRectForRepaint(RenderBox* repaintContainer, bool clipToVisibleContent)
|
| -{
|
| - ASSERT(!needsLayout());
|
| -
|
| - if (selectionState() == SelectionNone)
|
| - return IntRect();
|
| - RenderBlock* cb = containingBlock();
|
| - if (!cb)
|
| - return IntRect();
|
| -
|
| - // Now calculate startPos and endPos for painting selection.
|
| - // We include a selection while endPos > 0
|
| - int startPos, endPos;
|
| - if (selectionState() == SelectionInside) {
|
| - // We are fully selected.
|
| - startPos = 0;
|
| - endPos = textLength();
|
| - } else {
|
| - selectionStartEnd(startPos, endPos);
|
| - if (selectionState() == SelectionStart)
|
| - endPos = textLength();
|
| - else if (selectionState() == SelectionEnd)
|
| - startPos = 0;
|
| - }
|
| -
|
| - if (startPos == endPos)
|
| - return IntRect();
|
| -
|
| - IntRect rect;
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - rect.unite(box->selectionRect(0, 0, startPos, endPos));
|
| -
|
| - if (clipToVisibleContent)
|
| - computeRectForRepaint(repaintContainer, rect);
|
| - else {
|
| - if (cb->hasColumns())
|
| - cb->adjustRectForColumns(rect);
|
| -
|
| - rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
|
| - }
|
| -
|
| - return rect;
|
| -}
|
| -
|
| -int RenderText::verticalPositionHint(bool firstLine) const
|
| -{
|
| - if (parent()->isReplaced())
|
| - return 0; // Treat inline blocks just like blocks. There can't be any vertical position hint.
|
| - return parent()->verticalPositionHint(firstLine);
|
| -}
|
| -
|
| -int RenderText::caretMinOffset() const
|
| -{
|
| - InlineTextBox* box = firstTextBox();
|
| - if (!box)
|
| - return 0;
|
| - int minOffset = box->start();
|
| - for (box = box->nextTextBox(); box; box = box->nextTextBox())
|
| - minOffset = min<int>(minOffset, box->start());
|
| - return minOffset;
|
| -}
|
| -
|
| -int RenderText::caretMaxOffset() const
|
| -{
|
| - InlineTextBox* box = lastTextBox();
|
| - if (!box)
|
| - return textLength();
|
| - int maxOffset = box->start() + box->len();
|
| - for (box = box->prevTextBox(); box; box = box->prevTextBox())
|
| - maxOffset = max<int>(maxOffset, box->start() + box->len());
|
| - return maxOffset;
|
| -}
|
| -
|
| -unsigned RenderText::caretMaxRenderedOffset() const
|
| -{
|
| - int l = 0;
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - l += box->len();
|
| - return l;
|
| -}
|
| -
|
| -int RenderText::previousOffset(int current) const
|
| -{
|
| - StringImpl* si = m_text.get();
|
| - TextBreakIterator* iterator = characterBreakIterator(si->characters(), si->length());
|
| - if (!iterator)
|
| - return current - 1;
|
| -
|
| - long result = textBreakPreceding(iterator, current);
|
| - if (result == TextBreakDone)
|
| - result = current - 1;
|
| -
|
| - return result;
|
| -}
|
| -
|
| -int RenderText::nextOffset(int current) const
|
| -{
|
| - StringImpl* si = m_text.get();
|
| - TextBreakIterator* iterator = characterBreakIterator(si->characters(), si->length());
|
| - if (!iterator)
|
| - return current + 1;
|
| -
|
| - long result = textBreakFollowing(iterator, current);
|
| - if (result == TextBreakDone)
|
| - result = current + 1;
|
| -
|
| - return result;
|
| -}
|
| -
|
| -#ifndef NDEBUG
|
| -
|
| -void RenderText::checkConsistency() const
|
| -{
|
| -#ifdef CHECK_CONSISTENCY
|
| - const InlineTextBox* prev = 0;
|
| - for (const InlineTextBox* child = m_firstTextBox; child != 0; child = child->nextTextBox()) {
|
| - ASSERT(child->object() == this);
|
| - ASSERT(child->prevTextBox() == prev);
|
| - prev = child;
|
| - }
|
| - ASSERT(prev == m_lastTextBox);
|
| -#endif
|
| -}
|
| -
|
| -#endif
|
| -
|
| -} // namespace WebCore
|
| +/*
|
| + * (C) 1999 Lars Knoll (knoll@kde.org)
|
| + * (C) 2000 Dirk Mueller (mueller@kde.org)
|
| + * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
|
| + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
|
| + * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com)
|
| + *
|
| + * This library is free software; you can redistribute it and/or
|
| + * modify it under the terms of the GNU Library General Public
|
| + * License as published by the Free Software Foundation; either
|
| + * version 2 of the License, or (at your option) any later version.
|
| + *
|
| + * This library is distributed in the hope that it will be useful,
|
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
| + * Library General Public License for more details.
|
| + *
|
| + * You should have received a copy of the GNU Library General Public License
|
| + * along with this library; see the file COPYING.LIB. If not, write to
|
| + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
| + * Boston, MA 02110-1301, USA.
|
| + *
|
| + */
|
| +
|
| +#include "config.h"
|
| +#include "RenderText.h"
|
| +
|
| +#include "CharacterNames.h"
|
| +#include "FloatQuad.h"
|
| +#include "FrameView.h"
|
| +#include "InlineTextBox.h"
|
| +#include "Range.h"
|
| +#include "RenderArena.h"
|
| +#include "RenderBlock.h"
|
| +#include "RenderLayer.h"
|
| +#include "RenderView.h"
|
| +#include "Text.h"
|
| +#include "TextBreakIterator.h"
|
| +#include "break_lines.h"
|
| +#include <wtf/AlwaysInline.h>
|
| +
|
| +using namespace std;
|
| +using namespace WTF;
|
| +using namespace Unicode;
|
| +
|
| +namespace WebCore {
|
| +
|
| +// FIXME: Move to StringImpl.h eventually.
|
| +static inline bool charactersAreAllASCII(StringImpl* text)
|
| +{
|
| + return charactersAreAllASCII(text->characters(), text->length());
|
| +}
|
| +
|
| +RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str)
|
| + : RenderObject(node)
|
| + , m_text(str)
|
| + , m_firstTextBox(0)
|
| + , m_lastTextBox(0)
|
| + , m_minWidth(-1)
|
| + , m_maxWidth(-1)
|
| + , m_beginMinWidth(0)
|
| + , m_endMinWidth(0)
|
| + , m_hasTab(false)
|
| + , m_linesDirty(false)
|
| + , m_containsReversedText(false)
|
| + , m_isAllASCII(charactersAreAllASCII(m_text.get()))
|
| +{
|
| + ASSERT(m_text);
|
| + setIsText();
|
| + m_text = document()->displayStringModifiedByEncoding(PassRefPtr<StringImpl>(m_text));
|
| +
|
| + view()->frameView()->setIsVisuallyNonEmpty();
|
| +}
|
| +
|
| +#ifndef NDEBUG
|
| +
|
| +RenderText::~RenderText()
|
| +{
|
| + ASSERT(!m_firstTextBox);
|
| + ASSERT(!m_lastTextBox);
|
| +}
|
| +
|
| +#endif
|
| +
|
| +const char* RenderText::renderName() const
|
| +{
|
| + return "RenderText";
|
| +}
|
| +
|
| +bool RenderText::isTextFragment() const
|
| +{
|
| + return false;
|
| +}
|
| +
|
| +bool RenderText::isWordBreak() const
|
| +{
|
| + return false;
|
| +}
|
| +
|
| +void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
|
| +{
|
| + // There is no need to ever schedule repaints from a style change of a text run, since
|
| + // we already did this for the parent of the text run.
|
| + // We do have to schedule layouts, though, since a style change can force us to
|
| + // need to relayout.
|
| + if (diff == StyleDifferenceLayout)
|
| + setNeedsLayoutAndPrefWidthsRecalc();
|
| +
|
| + ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE;
|
| + ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE;
|
| +
|
| + if (oldTransform != style()->textTransform() || oldSecurity != style()->textSecurity()) {
|
| + if (RefPtr<StringImpl> textToTransform = originalText())
|
| + setText(textToTransform.release(), true);
|
| + }
|
| +}
|
| +
|
| +void RenderText::destroy()
|
| +{
|
| + if (!documentBeingDestroyed()) {
|
| + if (firstTextBox()) {
|
| + if (isBR()) {
|
| + RootInlineBox* next = firstTextBox()->root()->nextRootBox();
|
| + if (next)
|
| + next->markDirty();
|
| + }
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + box->remove();
|
| + } else if (parent())
|
| + parent()->dirtyLinesFromChangedChild(this);
|
| + }
|
| + deleteTextBoxes();
|
| + RenderObject::destroy();
|
| +}
|
| +
|
| +void RenderText::extractTextBox(InlineTextBox* box)
|
| +{
|
| + checkConsistency();
|
| +
|
| + m_lastTextBox = box->prevTextBox();
|
| + if (box == m_firstTextBox)
|
| + m_firstTextBox = 0;
|
| + if (box->prevTextBox())
|
| + box->prevTextBox()->setNextLineBox(0);
|
| + box->setPreviousLineBox(0);
|
| + for (InlineRunBox* curr = box; curr; curr = curr->nextLineBox())
|
| + curr->setExtracted();
|
| +
|
| + checkConsistency();
|
| +}
|
| +
|
| +void RenderText::attachTextBox(InlineTextBox* box)
|
| +{
|
| + checkConsistency();
|
| +
|
| + if (m_lastTextBox) {
|
| + m_lastTextBox->setNextLineBox(box);
|
| + box->setPreviousLineBox(m_lastTextBox);
|
| + } else
|
| + m_firstTextBox = box;
|
| + InlineTextBox* last = box;
|
| + for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) {
|
| + curr->setExtracted(false);
|
| + last = curr;
|
| + }
|
| + m_lastTextBox = last;
|
| +
|
| + checkConsistency();
|
| +}
|
| +
|
| +void RenderText::removeTextBox(InlineTextBox* box)
|
| +{
|
| + checkConsistency();
|
| +
|
| + if (box == m_firstTextBox)
|
| + m_firstTextBox = box->nextTextBox();
|
| + if (box == m_lastTextBox)
|
| + m_lastTextBox = box->prevTextBox();
|
| + if (box->nextTextBox())
|
| + box->nextTextBox()->setPreviousLineBox(box->prevTextBox());
|
| + if (box->prevTextBox())
|
| + box->prevTextBox()->setNextLineBox(box->nextTextBox());
|
| +
|
| + checkConsistency();
|
| +}
|
| +
|
| +void RenderText::deleteTextBoxes()
|
| +{
|
| + if (firstTextBox()) {
|
| + RenderArena* arena = renderArena();
|
| + InlineTextBox* next;
|
| + for (InlineTextBox* curr = firstTextBox(); curr; curr = next) {
|
| + next = curr->nextTextBox();
|
| + curr->destroy(arena);
|
| + }
|
| + m_firstTextBox = m_lastTextBox = 0;
|
| + }
|
| +}
|
| +
|
| +PassRefPtr<StringImpl> RenderText::originalText() const
|
| +{
|
| + Node* e = element();
|
| + return e ? static_cast<Text*>(e)->string() : 0;
|
| +}
|
| +
|
| +void RenderText::absoluteRects(Vector<IntRect>& rects, int tx, int ty, bool)
|
| +{
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + rects.append(IntRect(tx + box->xPos(), ty + box->yPos(), box->width(), box->height()));
|
| +}
|
| +
|
| +void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight)
|
| +{
|
| + // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
|
| + // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
|
| + // function to take ints causes various internal mismatches. But selectionRect takes ints, and
|
| + // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
|
| + // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
|
| + ASSERT(end == UINT_MAX || end <= INT_MAX);
|
| + ASSERT(start <= INT_MAX);
|
| + start = min(start, static_cast<unsigned>(INT_MAX));
|
| + end = min(end, static_cast<unsigned>(INT_MAX));
|
| +
|
| + FloatPoint absPos = localToAbsolute(FloatPoint());
|
| +
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| + // Note: box->end() returns the index of the last character, not the index past it
|
| + if (start <= box->start() && box->end() < end) {
|
| + IntRect r = IntRect(absPos.x() + box->xPos(), absPos.y() + box->yPos(), box->width(), box->height());
|
| + if (useSelectionHeight) {
|
| + IntRect selectionRect = box->selectionRect(absPos.x(), absPos.y(), start, end);
|
| + r.setHeight(selectionRect.height());
|
| + r.setY(selectionRect.y());
|
| + }
|
| + rects.append(r);
|
| + } else {
|
| + unsigned realEnd = min(box->end() + 1, end);
|
| + IntRect r = box->selectionRect(absPos.x(), absPos.y(), start, realEnd);
|
| + if (!r.isEmpty()) {
|
| + if (!useSelectionHeight) {
|
| + // change the height and y position because selectionRect uses selection-specific values
|
| + r.setHeight(box->height());
|
| + r.setY(absPos.y() + box->yPos());
|
| + }
|
| + rects.append(r);
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool)
|
| +{
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + quads.append(localToAbsoluteQuad(FloatRect(box->xPos(), box->yPos(), box->width(), box->height())));
|
| +}
|
| +
|
| +void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight)
|
| +{
|
| + // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
|
| + // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
|
| + // function to take ints causes various internal mismatches. But selectionRect takes ints, and
|
| + // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
|
| + // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
|
| + ASSERT(end == UINT_MAX || end <= INT_MAX);
|
| + ASSERT(start <= INT_MAX);
|
| + start = min(start, static_cast<unsigned>(INT_MAX));
|
| + end = min(end, static_cast<unsigned>(INT_MAX));
|
| +
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| + // Note: box->end() returns the index of the last character, not the index past it
|
| + if (start <= box->start() && box->end() < end) {
|
| + IntRect r = IntRect(box->xPos(), box->yPos(), box->width(), box->height());
|
| + if (useSelectionHeight) {
|
| + IntRect selectionRect = box->selectionRect(0, 0, start, end);
|
| + r.setHeight(selectionRect.height());
|
| + r.setY(selectionRect.y());
|
| + }
|
| + quads.append(localToAbsoluteQuad(FloatRect(r)));
|
| + } else {
|
| + unsigned realEnd = min(box->end() + 1, end);
|
| + IntRect r = box->selectionRect(0, 0, start, realEnd);
|
| + if (!r.isEmpty()) {
|
| + if (!useSelectionHeight) {
|
| + // change the height and y position because selectionRect uses selection-specific values
|
| + r.setHeight(box->height());
|
| + r.setY(box->yPos());
|
| + }
|
| + quads.append(localToAbsoluteQuad(FloatRect(r)));
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +InlineTextBox* RenderText::findNextInlineTextBox(int offset, int& pos) const
|
| +{
|
| + // The text runs point to parts of the RenderText's m_text
|
| + // (they don't include '\n')
|
| + // Find the text run that includes the character at offset
|
| + // and return pos, which is the position of the char in the run.
|
| +
|
| + if (!m_firstTextBox)
|
| + return 0;
|
| +
|
| + InlineTextBox* s = m_firstTextBox;
|
| + int off = s->len();
|
| + while (offset > off && s->nextTextBox()) {
|
| + s = s->nextTextBox();
|
| + off = s->start() + s->len();
|
| + }
|
| + // we are now in the correct text run
|
| + pos = (offset > off ? s->len() : s->len() - (off - offset) );
|
| + return s;
|
| +}
|
| +
|
| +VisiblePosition RenderText::positionForCoordinates(int x, int y)
|
| +{
|
| + if (!firstTextBox() || textLength() == 0)
|
| + return VisiblePosition(element(), 0, DOWNSTREAM);
|
| +
|
| + // Get the offset for the position, since this will take rtl text into account.
|
| + int offset;
|
| +
|
| + // FIXME: We should be able to roll these special cases into the general cases in the loop below.
|
| + if (firstTextBox() && y < firstTextBox()->root()->bottomOverflow() && x < firstTextBox()->m_x) {
|
| + // at the y coordinate of the first line or above
|
| + // and the x coordinate is to the left of the first text box left edge
|
| + offset = firstTextBox()->offsetForPosition(x);
|
| + return VisiblePosition(element(), offset + firstTextBox()->start(), DOWNSTREAM);
|
| + }
|
| + if (lastTextBox() && y >= lastTextBox()->root()->topOverflow() && x >= lastTextBox()->m_x + lastTextBox()->m_width) {
|
| + // at the y coordinate of the last line or below
|
| + // and the x coordinate is to the right of the last text box right edge
|
| + offset = lastTextBox()->offsetForPosition(x);
|
| + return VisiblePosition(element(), offset + lastTextBox()->start(), DOWNSTREAM);
|
| + }
|
| +
|
| + InlineTextBox* lastBoxAbove = 0;
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| + if (y >= box->root()->topOverflow()) {
|
| + int bottom = box->root()->nextRootBox() ? box->root()->nextRootBox()->topOverflow() : box->root()->bottomOverflow();
|
| + if (y < bottom) {
|
| + offset = box->offsetForPosition(x);
|
| +
|
| + if (x == box->m_x)
|
| + // the x coordinate is equal to the left edge of this box
|
| + // the affinity must be downstream so the position doesn't jump back to the previous line
|
| + return VisiblePosition(element(), offset + box->start(), DOWNSTREAM);
|
| +
|
| + if (x < box->m_x + box->m_width)
|
| + // and the x coordinate is to the left of the right edge of this box
|
| + // check to see if position goes in this box
|
| + return VisiblePosition(element(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
|
| +
|
| + if (!box->prevOnLine() && x < box->m_x)
|
| + // box is first on line
|
| + // and the x coordinate is to the left of the first text box left edge
|
| + return VisiblePosition(element(), offset + box->start(), DOWNSTREAM);
|
| +
|
| + if (!box->nextOnLine())
|
| + // box is last on line
|
| + // and the x coordinate is to the right of the last text box right edge
|
| + // generate VisiblePosition, use UPSTREAM affinity if possible
|
| + return VisiblePosition(element(), offset + box->start(), offset > 0 ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM);
|
| + }
|
| + lastBoxAbove = box;
|
| + }
|
| + }
|
| +
|
| + return VisiblePosition(element(), lastBoxAbove ? lastBoxAbove->start() + lastBoxAbove->len() : 0, DOWNSTREAM);
|
| +}
|
| +
|
| +IntRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, int* extraWidthToEndOfLine)
|
| +{
|
| + if (!inlineBox)
|
| + return IntRect();
|
| +
|
| + ASSERT(inlineBox->isInlineTextBox());
|
| + if (!inlineBox->isInlineTextBox())
|
| + return IntRect();
|
| +
|
| + InlineTextBox* box = static_cast<InlineTextBox*>(inlineBox);
|
| +
|
| + int height = box->root()->bottomOverflow() - box->root()->topOverflow();
|
| + int top = box->root()->topOverflow();
|
| +
|
| + int left = box->positionForOffset(caretOffset);
|
| +
|
| + int rootLeft = box->root()->xPos();
|
| + // FIXME: should we use the width of the root inline box or the
|
| + // width of the containing block for this?
|
| + if (extraWidthToEndOfLine)
|
| + *extraWidthToEndOfLine = (box->root()->width() + rootLeft) - (left + 1);
|
| +
|
| + RenderBlock* cb = containingBlock();
|
| + if (style()->autoWrap()) {
|
| + int availableWidth = cb->lineWidth(top, false);
|
| + if (box->direction() == LTR)
|
| + left = min(left, rootLeft + availableWidth - 1);
|
| + else
|
| + left = max(left, rootLeft);
|
| + }
|
| +
|
| + const int caretWidth = 1;
|
| + return IntRect(left, top, caretWidth, height);
|
| +}
|
| +
|
| +ALWAYS_INLINE int RenderText::widthFromCache(const Font& f, int start, int len, int xPos) const
|
| +{
|
| + if (f.isFixedPitch() && !f.isSmallCaps() && m_isAllASCII) {
|
| + int monospaceCharacterWidth = f.spaceWidth();
|
| + int tabWidth = allowTabs() ? monospaceCharacterWidth * 8 : 0;
|
| + int w = 0;
|
| + bool isSpace;
|
| + bool previousCharWasSpace = true; // FIXME: Preserves historical behavior, but seems wrong for start > 0.
|
| + for (int i = start; i < start + len; i++) {
|
| + char c = (*m_text)[i];
|
| + if (c <= ' ') {
|
| + if (c == ' ' || c == '\n') {
|
| + w += monospaceCharacterWidth;
|
| + isSpace = true;
|
| + } else if (c == '\t') {
|
| + w += tabWidth ? tabWidth - ((xPos + w) % tabWidth) : monospaceCharacterWidth;
|
| + isSpace = true;
|
| + } else
|
| + isSpace = false;
|
| + } else {
|
| + w += monospaceCharacterWidth;
|
| + isSpace = false;
|
| + }
|
| + if (isSpace && !previousCharWasSpace)
|
| + w += f.wordSpacing();
|
| + previousCharWasSpace = isSpace;
|
| + }
|
| + return w;
|
| + }
|
| +
|
| + return f.width(TextRun(text()->characters() + start, len, allowTabs(), xPos));
|
| +}
|
| +
|
| +void RenderText::trimmedPrefWidths(int leadWidth,
|
| + int& beginMinW, bool& beginWS,
|
| + int& endMinW, bool& endWS,
|
| + bool& hasBreakableChar, bool& hasBreak,
|
| + int& beginMaxW, int& endMaxW,
|
| + int& minW, int& maxW, bool& stripFrontSpaces)
|
| +{
|
| + bool collapseWhiteSpace = style()->collapseWhiteSpace();
|
| + if (!collapseWhiteSpace)
|
| + stripFrontSpaces = false;
|
| +
|
| + if (m_hasTab || prefWidthsDirty())
|
| + calcPrefWidths(leadWidth);
|
| +
|
| + beginWS = !stripFrontSpaces && m_hasBeginWS;
|
| + endWS = m_hasEndWS;
|
| +
|
| + int len = textLength();
|
| +
|
| + if (!len || (stripFrontSpaces && m_text->containsOnlyWhitespace())) {
|
| + beginMinW = 0;
|
| + endMinW = 0;
|
| + beginMaxW = 0;
|
| + endMaxW = 0;
|
| + minW = 0;
|
| + maxW = 0;
|
| + hasBreak = false;
|
| + return;
|
| + }
|
| +
|
| + minW = m_minWidth;
|
| + maxW = m_maxWidth;
|
| +
|
| + beginMinW = m_beginMinWidth;
|
| + endMinW = m_endMinWidth;
|
| +
|
| + hasBreakableChar = m_hasBreakableChar;
|
| + hasBreak = m_hasBreak;
|
| +
|
| + if ((*m_text)[0] == ' ' || ((*m_text)[0] == '\n' && !style()->preserveNewline()) || (*m_text)[0] == '\t') {
|
| + const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| + if (stripFrontSpaces) {
|
| + const UChar space = ' ';
|
| + int spaceWidth = f.width(TextRun(&space, 1));
|
| + maxW -= spaceWidth;
|
| + } else
|
| + maxW += f.wordSpacing();
|
| + }
|
| +
|
| + stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
|
| +
|
| + if (!style()->autoWrap() || minW > maxW)
|
| + minW = maxW;
|
| +
|
| + // Compute our max widths by scanning the string for newlines.
|
| + if (hasBreak) {
|
| + const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| + bool firstLine = true;
|
| + beginMaxW = maxW;
|
| + endMaxW = maxW;
|
| + for (int i = 0; i < len; i++) {
|
| + int linelen = 0;
|
| + while (i + linelen < len && (*m_text)[i + linelen] != '\n')
|
| + linelen++;
|
| +
|
| + if (linelen) {
|
| + endMaxW = widthFromCache(f, i, linelen, leadWidth + endMaxW);
|
| + if (firstLine) {
|
| + firstLine = false;
|
| + leadWidth = 0;
|
| + beginMaxW = endMaxW;
|
| + }
|
| + i += linelen;
|
| + } else if (firstLine) {
|
| + beginMaxW = 0;
|
| + firstLine = false;
|
| + leadWidth = 0;
|
| + }
|
| +
|
| + if (i == len - 1)
|
| + // A <pre> run that ends with a newline, as in, e.g.,
|
| + // <pre>Some text\n\n<span>More text</pre>
|
| + endMaxW = 0;
|
| + }
|
| + }
|
| +}
|
| +
|
| +static inline bool isSpaceAccordingToStyle(UChar c, RenderStyle* style)
|
| +{
|
| + return c == ' ' || (c == noBreakSpace && style->nbspMode() == SPACE);
|
| +}
|
| +
|
| +int RenderText::minPrefWidth() const
|
| +{
|
| + if (prefWidthsDirty())
|
| + const_cast<RenderText*>(this)->calcPrefWidths(0);
|
| +
|
| + return m_minWidth;
|
| +}
|
| +
|
| +int RenderText::maxPrefWidth() const
|
| +{
|
| + if (prefWidthsDirty())
|
| + const_cast<RenderText*>(this)->calcPrefWidths(0);
|
| +
|
| + return m_maxWidth;
|
| +}
|
| +
|
| +void RenderText::calcPrefWidths(int leadWidth)
|
| +{
|
| + ASSERT(m_hasTab || prefWidthsDirty());
|
| +
|
| + m_minWidth = 0;
|
| + m_beginMinWidth = 0;
|
| + m_endMinWidth = 0;
|
| + m_maxWidth = 0;
|
| +
|
| + if (isBR())
|
| + return;
|
| +
|
| + int currMinWidth = 0;
|
| + int currMaxWidth = 0;
|
| + m_hasBreakableChar = false;
|
| + m_hasBreak = false;
|
| + m_hasTab = false;
|
| + m_hasBeginWS = false;
|
| + m_hasEndWS = false;
|
| +
|
| + const Font& f = style()->font(); // FIXME: This ignores first-line.
|
| + int wordSpacing = style()->wordSpacing();
|
| + int len = textLength();
|
| + const UChar* txt = characters();
|
| + bool needsWordSpacing = false;
|
| + bool ignoringSpaces = false;
|
| + bool isSpace = false;
|
| + bool firstWord = true;
|
| + bool firstLine = true;
|
| + int nextBreakable = -1;
|
| + int lastWordBoundary = 0;
|
| +
|
| + bool breakNBSP = style()->autoWrap() && style()->nbspMode() == SPACE;
|
| + bool breakAll = (style()->wordBreak() == BreakAllWordBreak || style()->wordBreak() == BreakWordBreak) && style()->autoWrap();
|
| +
|
| + for (int i = 0; i < len; i++) {
|
| + UChar c = txt[i];
|
| +
|
| + bool previousCharacterIsSpace = isSpace;
|
| +
|
| + bool isNewline = false;
|
| + if (c == '\n') {
|
| + if (style()->preserveNewline()) {
|
| + m_hasBreak = true;
|
| + isNewline = true;
|
| + isSpace = false;
|
| + } else
|
| + isSpace = true;
|
| + } else if (c == '\t') {
|
| + if (!style()->collapseWhiteSpace()) {
|
| + m_hasTab = true;
|
| + isSpace = false;
|
| + } else
|
| + isSpace = true;
|
| + } else
|
| + isSpace = c == ' ';
|
| +
|
| + if ((isSpace || isNewline) && !i)
|
| + m_hasBeginWS = true;
|
| + if ((isSpace || isNewline) && i == len - 1)
|
| + m_hasEndWS = true;
|
| +
|
| + if (!ignoringSpaces && style()->collapseWhiteSpace() && previousCharacterIsSpace && isSpace)
|
| + ignoringSpaces = true;
|
| +
|
| + if (ignoringSpaces && !isSpace)
|
| + ignoringSpaces = false;
|
| +
|
| + // Ignore spaces and soft hyphens
|
| + if (ignoringSpaces) {
|
| + ASSERT(lastWordBoundary == i);
|
| + lastWordBoundary++;
|
| + continue;
|
| + } else if (c == softHyphen) {
|
| + currMaxWidth += widthFromCache(f, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth);
|
| + lastWordBoundary = i + 1;
|
| + continue;
|
| + }
|
| +
|
| + bool hasBreak = breakAll || isBreakable(txt, i, len, nextBreakable, breakNBSP);
|
| + bool betweenWords = true;
|
| + int j = i;
|
| + while (c != '\n' && !isSpaceAccordingToStyle(c, style()) && c != '\t' && c != softHyphen) {
|
| + j++;
|
| + if (j == len)
|
| + break;
|
| + c = txt[j];
|
| + if (isBreakable(txt, j, len, nextBreakable, breakNBSP))
|
| + break;
|
| + if (breakAll) {
|
| + betweenWords = false;
|
| + break;
|
| + }
|
| + }
|
| +
|
| + int wordLen = j - i;
|
| + if (wordLen) {
|
| + int w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth);
|
| + currMinWidth += w;
|
| + if (betweenWords) {
|
| + if (lastWordBoundary == i)
|
| + currMaxWidth += w;
|
| + else
|
| + currMaxWidth += widthFromCache(f, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth);
|
| + lastWordBoundary = j;
|
| + }
|
| +
|
| + bool isSpace = (j < len) && isSpaceAccordingToStyle(c, style());
|
| + bool isCollapsibleWhiteSpace = (j < len) && style()->isCollapsibleWhiteSpace(c);
|
| + if (j < len && style()->autoWrap())
|
| + m_hasBreakableChar = true;
|
| +
|
| + // Add in wordSpacing to our currMaxWidth, but not if this is the last word on a line or the
|
| + // last word in the run.
|
| + if (wordSpacing && (isSpace || isCollapsibleWhiteSpace) && !containsOnlyWhitespace(j, len-j))
|
| + currMaxWidth += wordSpacing;
|
| +
|
| + if (firstWord) {
|
| + firstWord = false;
|
| + // If the first character in the run is breakable, then we consider ourselves to have a beginning
|
| + // minimum width of 0, since a break could occur right before our run starts, preventing us from ever
|
| + // being appended to a previous text run when considering the total minimum width of the containing block.
|
| + if (hasBreak)
|
| + m_hasBreakableChar = true;
|
| + m_beginMinWidth = hasBreak ? 0 : w;
|
| + }
|
| + m_endMinWidth = w;
|
| +
|
| + if (currMinWidth > m_minWidth)
|
| + m_minWidth = currMinWidth;
|
| + currMinWidth = 0;
|
| +
|
| + i += wordLen - 1;
|
| + } else {
|
| + // Nowrap can never be broken, so don't bother setting the
|
| + // breakable character boolean. Pre can only be broken if we encounter a newline.
|
| + if (style()->autoWrap() || isNewline)
|
| + m_hasBreakableChar = true;
|
| +
|
| + if (currMinWidth > m_minWidth)
|
| + m_minWidth = currMinWidth;
|
| + currMinWidth = 0;
|
| +
|
| + if (isNewline) { // Only set if preserveNewline was true and we saw a newline.
|
| + if (firstLine) {
|
| + firstLine = false;
|
| + leadWidth = 0;
|
| + if (!style()->autoWrap())
|
| + m_beginMinWidth = currMaxWidth;
|
| + }
|
| +
|
| + if (currMaxWidth > m_maxWidth)
|
| + m_maxWidth = currMaxWidth;
|
| + currMaxWidth = 0;
|
| + } else {
|
| + currMaxWidth += f.width(TextRun(txt + i, 1, allowTabs(), leadWidth + currMaxWidth));
|
| + needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len - 1;
|
| + }
|
| + ASSERT(lastWordBoundary == i);
|
| + lastWordBoundary++;
|
| + }
|
| + }
|
| +
|
| + if (needsWordSpacing && len > 1 || ignoringSpaces && !firstWord)
|
| + currMaxWidth += wordSpacing;
|
| +
|
| + m_minWidth = max(currMinWidth, m_minWidth);
|
| + m_maxWidth = max(currMaxWidth, m_maxWidth);
|
| +
|
| + if (!style()->autoWrap())
|
| + m_minWidth = m_maxWidth;
|
| +
|
| + if (style()->whiteSpace() == PRE) {
|
| + if (firstLine)
|
| + m_beginMinWidth = m_maxWidth;
|
| + m_endMinWidth = currMaxWidth;
|
| + }
|
| +
|
| + setPrefWidthsDirty(false);
|
| +}
|
| +
|
| +bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
|
| +{
|
| + unsigned currPos;
|
| + for (currPos = from;
|
| + currPos < from + len && ((*m_text)[currPos] == '\n' || (*m_text)[currPos] == ' ' || (*m_text)[currPos] == '\t');
|
| + currPos++) { }
|
| + return currPos >= (from + len);
|
| +}
|
| +
|
| +int RenderText::firstRunX() const
|
| +{
|
| + return m_firstTextBox ? m_firstTextBox->m_x : 0;
|
| +}
|
| +
|
| +int RenderText::firstRunY() const
|
| +{
|
| + return m_firstTextBox ? m_firstTextBox->m_y : 0;
|
| +}
|
| +
|
| +void RenderText::setSelectionState(SelectionState state)
|
| +{
|
| + InlineTextBox* box;
|
| +
|
| + RenderObject::setSelectionState(state);
|
| + if (state == SelectionStart || state == SelectionEnd || state == SelectionBoth) {
|
| + int startPos, endPos;
|
| + selectionStartEnd(startPos, endPos);
|
| + if (selectionState() == SelectionStart) {
|
| + endPos = textLength();
|
| +
|
| + // to handle selection from end of text to end of line
|
| + if (startPos != 0 && startPos == endPos)
|
| + startPos = endPos - 1;
|
| + } else if (selectionState() == SelectionEnd)
|
| + startPos = 0;
|
| +
|
| + for (box = firstTextBox(); box; box = box->nextTextBox()) {
|
| + if (box->isSelected(startPos, endPos)) {
|
| + RootInlineBox* line = box->root();
|
| + if (line)
|
| + line->setHasSelectedChildren(true);
|
| + }
|
| + }
|
| + } else {
|
| + for (box = firstTextBox(); box; box = box->nextTextBox()) {
|
| + RootInlineBox* line = box->root();
|
| + if (line)
|
| + line->setHasSelectedChildren(state == SelectionInside);
|
| + }
|
| + }
|
| +
|
| + containingBlock()->setSelectionState(state);
|
| +}
|
| +
|
| +void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset, unsigned len, bool force)
|
| +{
|
| + unsigned oldLen = textLength();
|
| + unsigned newLen = text->length();
|
| + int delta = newLen - oldLen;
|
| + unsigned end = len ? offset + len - 1 : offset;
|
| +
|
| + RootInlineBox* firstRootBox = 0;
|
| + RootInlineBox* lastRootBox = 0;
|
| +
|
| + bool dirtiedLines = false;
|
| +
|
| + // Dirty all text boxes that include characters in between offset and offset+len.
|
| + for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| + // Text run is entirely before the affected range.
|
| + if (curr->end() < offset)
|
| + continue;
|
| +
|
| + // Text run is entirely after the affected range.
|
| + if (curr->start() > end) {
|
| + curr->offsetRun(delta);
|
| + RootInlineBox* root = curr->root();
|
| + if (!firstRootBox) {
|
| + firstRootBox = root;
|
| + if (!dirtiedLines) {
|
| + // The affected area was in between two runs. Go ahead and mark the root box of
|
| + // the run after the affected area as dirty.
|
| + firstRootBox->markDirty();
|
| + dirtiedLines = true;
|
| + }
|
| + }
|
| + lastRootBox = root;
|
| + } else if (curr->end() >= offset && curr->end() <= end) {
|
| + // Text run overlaps with the left end of the affected range.
|
| + curr->dirtyLineBoxes();
|
| + dirtiedLines = true;
|
| + } else if (curr->start() <= offset && curr->end() >= end) {
|
| + // Text run subsumes the affected range.
|
| + curr->dirtyLineBoxes();
|
| + dirtiedLines = true;
|
| + } else if (curr->start() <= end && curr->end() >= end) {
|
| + // Text run overlaps with right end of the affected range.
|
| + curr->dirtyLineBoxes();
|
| + dirtiedLines = true;
|
| + }
|
| + }
|
| +
|
| + // Now we have to walk all of the clean lines and adjust their cached line break information
|
| + // to reflect our updated offsets.
|
| + if (lastRootBox)
|
| + lastRootBox = lastRootBox->nextRootBox();
|
| + if (firstRootBox) {
|
| + RootInlineBox* prev = firstRootBox->prevRootBox();
|
| + if (prev)
|
| + firstRootBox = prev;
|
| + }
|
| + for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) {
|
| + if (curr->lineBreakObj() == this && curr->lineBreakPos() > end)
|
| + curr->setLineBreakPos(curr->lineBreakPos() + delta);
|
| + }
|
| +
|
| + // If the text node is empty, dirty the line where new text will be inserted.
|
| + if (!firstTextBox() && parent()) {
|
| + parent()->dirtyLinesFromChangedChild(this);
|
| + dirtiedLines = true;
|
| + }
|
| +
|
| + m_linesDirty = dirtiedLines;
|
| + setText(text, force);
|
| +}
|
| +
|
| +static inline bool isInlineFlowOrEmptyText(RenderObject* o)
|
| +{
|
| + if (o->isRenderInline())
|
| + return true;
|
| + if (!o->isText())
|
| + return false;
|
| + StringImpl* text = toRenderText(o)->text();
|
| + if (!text)
|
| + return true;
|
| + return !text->length();
|
| +}
|
| +
|
| +UChar RenderText::previousCharacter()
|
| +{
|
| + // find previous text renderer if one exists
|
| + RenderObject* previousText = this;
|
| + while ((previousText = previousText->previousInPreOrder()))
|
| + if (!isInlineFlowOrEmptyText(previousText))
|
| + break;
|
| + UChar prev = ' ';
|
| + if (previousText && previousText->isText())
|
| + if (StringImpl* previousString = toRenderText(previousText)->text())
|
| + prev = (*previousString)[previousString->length() - 1];
|
| + return prev;
|
| +}
|
| +
|
| +void RenderText::setTextInternal(PassRefPtr<StringImpl> text)
|
| +{
|
| + m_text = text;
|
| + ASSERT(m_text);
|
| +
|
| + m_text = document()->displayStringModifiedByEncoding(PassRefPtr<StringImpl>(m_text));
|
| +#if ENABLE(SVG)
|
| + if (isSVGText()) {
|
| + if (style() && style()->whiteSpace() == PRE) {
|
| + // Spec: When xml:space="preserve", the SVG user agent will do the following using a
|
| + // copy of the original character data content. It will convert all newline and tab
|
| + // characters into space characters. Then, it will draw all space characters, including
|
| + // leading, trailing and multiple contiguous space characters.
|
| +
|
| + m_text = m_text->replace('\n', ' ');
|
| +
|
| + // If xml:space="preserve" is set, white-space is set to "pre", which
|
| + // preserves leading, trailing & contiguous space character for us.
|
| + } else {
|
| + // Spec: When xml:space="default", the SVG user agent will do the following using a
|
| + // copy of the original character data content. First, it will remove all newline
|
| + // characters. Then it will convert all tab characters into space characters.
|
| + // Then, it will strip off all leading and trailing space characters.
|
| + // Then, all contiguous space characters will be consolidated.
|
| +
|
| + m_text = m_text->replace('\n', StringImpl::empty());
|
| +
|
| + // If xml:space="default" is set, white-space is set to "nowrap", which handles
|
| + // leading, trailing & contiguous space character removal for us.
|
| + }
|
| +
|
| + m_text = m_text->replace('\t', ' ');
|
| + }
|
| +#endif
|
| +
|
| + if (style()) {
|
| + switch (style()->textTransform()) {
|
| + case TTNONE:
|
| + break;
|
| + case CAPITALIZE: {
|
| + m_text = m_text->capitalize(previousCharacter());
|
| + break;
|
| + }
|
| + case UPPERCASE:
|
| + m_text = m_text->upper();
|
| + break;
|
| + case LOWERCASE:
|
| + m_text = m_text->lower();
|
| + break;
|
| + }
|
| +
|
| + // We use the same characters here as for list markers.
|
| + // See the listMarkerText function in RenderListMarker.cpp.
|
| + switch (style()->textSecurity()) {
|
| + case TSNONE:
|
| + break;
|
| + case TSCIRCLE:
|
| + m_text = m_text->secure(whiteBullet);
|
| + break;
|
| + case TSDISC:
|
| + m_text = m_text->secure(bullet);
|
| + break;
|
| + case TSSQUARE:
|
| + m_text = m_text->secure(blackSquare);
|
| + }
|
| + }
|
| +
|
| + ASSERT(m_text);
|
| + ASSERT(!isBR() || (textLength() == 1 && (*m_text)[0] == '\n'));
|
| +
|
| + m_isAllASCII = charactersAreAllASCII(m_text.get());
|
| +}
|
| +
|
| +void RenderText::setText(PassRefPtr<StringImpl> text, bool force)
|
| +{
|
| + ASSERT(text);
|
| +
|
| + if (!force && equal(m_text.get(), text.get()))
|
| + return;
|
| +
|
| + setTextInternal(text);
|
| + setNeedsLayoutAndPrefWidthsRecalc();
|
| +}
|
| +
|
| +int RenderText::lineHeight(bool firstLine, bool) const
|
| +{
|
| + // Always use the interior line height of the parent (e.g., if our parent is an inline block).
|
| + return parent()->lineHeight(firstLine, true);
|
| +}
|
| +
|
| +void RenderText::dirtyLineBoxes(bool fullLayout, bool)
|
| +{
|
| + if (fullLayout)
|
| + deleteTextBoxes();
|
| + else if (!m_linesDirty) {
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + box->dirtyLineBoxes();
|
| + }
|
| + m_linesDirty = false;
|
| +}
|
| +
|
| +InlineTextBox* RenderText::createInlineTextBox()
|
| +{
|
| + return new (renderArena()) InlineTextBox(this);
|
| +}
|
| +
|
| +InlineBox* RenderText::createInlineBox(bool, bool unusedIsRootLineBox, bool)
|
| +{
|
| + ASSERT_UNUSED(unusedIsRootLineBox, !unusedIsRootLineBox);
|
| +
|
| + InlineTextBox* textBox = createInlineTextBox();
|
| + if (!m_firstTextBox)
|
| + m_firstTextBox = m_lastTextBox = textBox;
|
| + else {
|
| + m_lastTextBox->setNextLineBox(textBox);
|
| + textBox->setPreviousLineBox(m_lastTextBox);
|
| + m_lastTextBox = textBox;
|
| + }
|
| + return textBox;
|
| +}
|
| +
|
| +void RenderText::position(InlineBox* box)
|
| +{
|
| + InlineTextBox* s = static_cast<InlineTextBox*>(box);
|
| +
|
| + // FIXME: should not be needed!!!
|
| + if (!s->len()) {
|
| + // We want the box to be destroyed.
|
| + s->remove();
|
| + s->destroy(renderArena());
|
| + m_firstTextBox = m_lastTextBox = 0;
|
| + return;
|
| + }
|
| +
|
| + m_containsReversedText |= s->direction() == RTL;
|
| +}
|
| +
|
| +unsigned RenderText::width(unsigned from, unsigned len, int xPos, bool firstLine) const
|
| +{
|
| + if (from >= textLength())
|
| + return 0;
|
| +
|
| + if (from + len > textLength())
|
| + len = textLength() - from;
|
| +
|
| + return width(from, len, style(firstLine)->font(), xPos);
|
| +}
|
| +
|
| +unsigned RenderText::width(unsigned from, unsigned len, const Font& f, int xPos) const
|
| +{
|
| + ASSERT(from + len <= textLength());
|
| + if (!characters())
|
| + return 0;
|
| +
|
| + int w;
|
| + if (&f == &style()->font()) {
|
| + if (!style()->preserveNewline() && !from && len == textLength())
|
| + w = maxPrefWidth();
|
| + else
|
| + w = widthFromCache(f, from, len, xPos);
|
| + } else
|
| + w = f.width(TextRun(text()->characters() + from, len, allowTabs(), xPos));
|
| +
|
| + return w;
|
| +}
|
| +
|
| +IntRect RenderText::linesBoundingBox() const
|
| +{
|
| + IntRect result;
|
| +
|
| + ASSERT(!firstTextBox() == !lastTextBox()); // Either both are null or both exist.
|
| + if (firstTextBox() && lastTextBox()) {
|
| + // Return the width of the minimal left side and the maximal right side.
|
| + int leftSide = 0;
|
| + int rightSide = 0;
|
| + for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| + if (curr == firstTextBox() || curr->xPos() < leftSide)
|
| + leftSide = curr->xPos();
|
| + if (curr == firstTextBox() || curr->xPos() + curr->width() > rightSide)
|
| + rightSide = curr->xPos() + curr->width();
|
| + }
|
| + result.setWidth(rightSide - leftSide);
|
| + result.setX(leftSide);
|
| + result.setHeight(lastTextBox()->yPos() + lastTextBox()->height() - firstTextBox()->yPos());
|
| + result.setY(firstTextBox()->yPos());
|
| + }
|
| +
|
| + return result;
|
| +}
|
| +
|
| +IntRect RenderText::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer)
|
| +{
|
| + RenderObject* cb = containingBlock();
|
| + return cb->clippedOverflowRectForRepaint(repaintContainer);
|
| +}
|
| +
|
| +IntRect RenderText::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent)
|
| +{
|
| + ASSERT(!needsLayout());
|
| +
|
| + if (selectionState() == SelectionNone)
|
| + return IntRect();
|
| + RenderBlock* cb = containingBlock();
|
| + if (!cb)
|
| + return IntRect();
|
| +
|
| + // Now calculate startPos and endPos for painting selection.
|
| + // We include a selection while endPos > 0
|
| + int startPos, endPos;
|
| + if (selectionState() == SelectionInside) {
|
| + // We are fully selected.
|
| + startPos = 0;
|
| + endPos = textLength();
|
| + } else {
|
| + selectionStartEnd(startPos, endPos);
|
| + if (selectionState() == SelectionStart)
|
| + endPos = textLength();
|
| + else if (selectionState() == SelectionEnd)
|
| + startPos = 0;
|
| + }
|
| +
|
| + if (startPos == endPos)
|
| + return IntRect();
|
| +
|
| + IntRect rect;
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + rect.unite(box->selectionRect(0, 0, startPos, endPos));
|
| +
|
| + if (clipToVisibleContent)
|
| + computeRectForRepaint(repaintContainer, rect);
|
| + else {
|
| + if (cb->hasColumns())
|
| + cb->adjustRectForColumns(rect);
|
| +
|
| + rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
|
| + }
|
| +
|
| + return rect;
|
| +}
|
| +
|
| +int RenderText::verticalPositionHint(bool firstLine) const
|
| +{
|
| + if (parent()->isReplaced())
|
| + return 0; // Treat inline blocks just like blocks. There can't be any vertical position hint.
|
| + return parent()->verticalPositionHint(firstLine);
|
| +}
|
| +
|
| +int RenderText::caretMinOffset() const
|
| +{
|
| + InlineTextBox* box = firstTextBox();
|
| + if (!box)
|
| + return 0;
|
| + int minOffset = box->start();
|
| + for (box = box->nextTextBox(); box; box = box->nextTextBox())
|
| + minOffset = min<int>(minOffset, box->start());
|
| + return minOffset;
|
| +}
|
| +
|
| +int RenderText::caretMaxOffset() const
|
| +{
|
| + InlineTextBox* box = lastTextBox();
|
| + if (!box)
|
| + return textLength();
|
| + int maxOffset = box->start() + box->len();
|
| + for (box = box->prevTextBox(); box; box = box->prevTextBox())
|
| + maxOffset = max<int>(maxOffset, box->start() + box->len());
|
| + return maxOffset;
|
| +}
|
| +
|
| +unsigned RenderText::caretMaxRenderedOffset() const
|
| +{
|
| + int l = 0;
|
| + for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| + l += box->len();
|
| + return l;
|
| +}
|
| +
|
| +int RenderText::previousOffset(int current) const
|
| +{
|
| + StringImpl* si = m_text.get();
|
| + TextBreakIterator* iterator = characterBreakIterator(si->characters(), si->length());
|
| + if (!iterator)
|
| + return current - 1;
|
| +
|
| + long result = textBreakPreceding(iterator, current);
|
| + if (result == TextBreakDone)
|
| + result = current - 1;
|
| +
|
| + return result;
|
| +}
|
| +
|
| +#define HANGUL_CHOSEONG_START (0x1100)
|
| +#define HANGUL_CHOSEONG_END (0x115F)
|
| +#define HANGUL_JUNGSEONG_START (0x1160)
|
| +#define HANGUL_JUNGSEONG_END (0x11A2)
|
| +#define HANGUL_JONGSEONG_START (0x11A8)
|
| +#define HANGUL_JONGSEONG_END (0x11F9)
|
| +#define HANGUL_SYLLABLE_START (0xAC00)
|
| +#define HANGUL_SYLLABLE_END (0xD7AF)
|
| +#define HANGUL_JONGSEONG_COUNT (28)
|
| +
|
| +enum HangulState {
|
| + HangulStateL,
|
| + HangulStateV,
|
| + HangulStateT,
|
| + HangulStateLV,
|
| + HangulStateLVT,
|
| + HangulStateBreak
|
| +};
|
| +
|
| +inline bool isHangulLVT(UChar32 character)
|
| +{
|
| + return (character - HANGUL_SYLLABLE_START) % HANGUL_JONGSEONG_COUNT;
|
| +}
|
| +
|
| +int RenderText::previousOffsetForBackwardDeletion(int current) const
|
| +{
|
| +#if PLATFORM(MAC)
|
| + UChar32 character;
|
| + while (current > 0) {
|
| + if (U16_IS_TRAIL((*m_text)[--current]))
|
| + --current;
|
| + if (current < 0)
|
| + break;
|
| +
|
| + UChar32 character = m_text->characterStartingAt(current);
|
| +
|
| + // We don't combine characters in Armenian ... Limbu range for backward deletion.
|
| + if ((character >= 0x0530) && (character < 0x1950))
|
| + break;
|
| +
|
| + if (u_isbase(character) && (character != 0xFF9E) && (character != 0xFF9F))
|
| + break;
|
| + }
|
| +
|
| + if (current <= 0)
|
| + return current;
|
| +
|
| + // Hangul
|
| + character = m_text->characterStartingAt(current);
|
| + if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))) {
|
| + HangulState state;
|
| + HangulState initialState;
|
| +
|
| + if (character < HANGUL_JUNGSEONG_START)
|
| + state = HangulStateL;
|
| + else if (character < HANGUL_JONGSEONG_START)
|
| + state = HangulStateV;
|
| + else if (character < HANGUL_SYLLABLE_START)
|
| + state = HangulStateT;
|
| + else
|
| + state = isHangulLVT(character) ? HangulStateLVT : HangulStateLV;
|
| +
|
| + initialState = state;
|
| +
|
| + while (current > 0 && ((character = m_text->characterStartingAt(current - 1)) >= HANGUL_CHOSEONG_START) && (character <= HANGUL_SYLLABLE_END) && ((character <= HANGUL_JONGSEONG_END) || (character >= HANGUL_SYLLABLE_START))) {
|
| + switch (state) {
|
| + case HangulStateV:
|
| + if (character <= HANGUL_CHOSEONG_END)
|
| + state = HangulStateL;
|
| + else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END) && !isHangulLVT(character))
|
| + state = HangulStateLV;
|
| + else if (character > HANGUL_JUNGSEONG_END)
|
| + state = HangulStateBreak;
|
| + break;
|
| + case HangulStateT:
|
| + if ((character >= HANGUL_JUNGSEONG_START) && (character <= HANGUL_JUNGSEONG_END))
|
| + state = HangulStateV;
|
| + else if ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))
|
| + state = (isHangulLVT(character) ? HangulStateLVT : HangulStateLV);
|
| + else if (character < HANGUL_JUNGSEONG_START)
|
| + state = HangulStateBreak;
|
| + break;
|
| + default:
|
| + state = (character < HANGUL_JUNGSEONG_START) ? HangulStateL : HangulStateBreak;
|
| + break;
|
| + }
|
| + if (state == HangulStateBreak)
|
| + break;
|
| +
|
| + --current;
|
| + }
|
| + }
|
| +
|
| + return current;
|
| +#else
|
| + // Platforms other than Mac delete by one code point.
|
| + return current - 1;
|
| +#endif
|
| +}
|
| +
|
| +int RenderText::nextOffset(int current) const
|
| +{
|
| + StringImpl* si = m_text.get();
|
| + TextBreakIterator* iterator = characterBreakIterator(si->characters(), si->length());
|
| + if (!iterator)
|
| + return current + 1;
|
| +
|
| + long result = textBreakFollowing(iterator, current);
|
| + if (result == TextBreakDone)
|
| + result = current + 1;
|
| +
|
| + return result;
|
| +}
|
| +
|
| +#ifndef NDEBUG
|
| +
|
| +void RenderText::checkConsistency() const
|
| +{
|
| +#ifdef CHECK_CONSISTENCY
|
| + const InlineTextBox* prev = 0;
|
| + for (const InlineTextBox* child = m_firstTextBox; child != 0; child = child->nextTextBox()) {
|
| + ASSERT(child->object() == this);
|
| + ASSERT(child->prevTextBox() == prev);
|
| + prev = child;
|
| + }
|
| + ASSERT(prev == m_lastTextBox);
|
| +#endif
|
| +}
|
| +
|
| +#endif
|
| +
|
| +} // namespace WebCore
|
|
|