| Index: Source/core/rendering/RenderText.cpp
|
| diff --git a/Source/core/rendering/RenderText.cpp b/Source/core/rendering/RenderText.cpp
|
| deleted file mode 100644
|
| index a959e3983fd81a5979aa4c051ee7298880a1d736..0000000000000000000000000000000000000000
|
| --- a/Source/core/rendering/RenderText.cpp
|
| +++ /dev/null
|
| @@ -1,1899 +0,0 @@
|
| -/*
|
| - * (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 "core/rendering/RenderText.h"
|
| -
|
| -#include "core/dom/AXObjectCache.h"
|
| -#include "core/dom/Text.h"
|
| -#include "core/editing/VisiblePosition.h"
|
| -#include "core/editing/iterators/TextIterator.h"
|
| -#include "core/frame/FrameView.h"
|
| -#include "core/frame/Settings.h"
|
| -#include "core/html/parser/TextResourceDecoder.h"
|
| -#include "core/layout/Layer.h"
|
| -#include "core/layout/LayoutBlock.h"
|
| -#include "core/layout/LayoutView.h"
|
| -#include "core/layout/TextRunConstructor.h"
|
| -#include "core/layout/line/AbstractInlineTextBox.h"
|
| -#include "core/layout/line/EllipsisBox.h"
|
| -#include "core/layout/line/InlineTextBox.h"
|
| -#include "core/rendering/RenderCombineText.h"
|
| -#include "platform/fonts/Character.h"
|
| -#include "platform/fonts/FontCache.h"
|
| -#include "platform/geometry/FloatQuad.h"
|
| -#include "platform/graphics/paint/DisplayItemList.h"
|
| -#include "platform/text/BidiResolver.h"
|
| -#include "platform/text/TextBreakIterator.h"
|
| -#include "platform/text/TextRunIterator.h"
|
| -#include "wtf/text/StringBuffer.h"
|
| -#include "wtf/text/StringBuilder.h"
|
| -#include "wtf/unicode/CharacterNames.h"
|
| -
|
| -using namespace WTF;
|
| -using namespace Unicode;
|
| -
|
| -namespace blink {
|
| -
|
| -struct SameSizeAsRenderText : public LayoutObject {
|
| - uint32_t bitfields : 16;
|
| - float widths[4];
|
| - String text;
|
| - void* pointers[2];
|
| -};
|
| -
|
| -static_assert(sizeof(RenderText) == sizeof(SameSizeAsRenderText), "RenderText should stay small");
|
| -
|
| -class SecureTextTimer;
|
| -typedef HashMap<RenderText*, SecureTextTimer*> SecureTextTimerMap;
|
| -static SecureTextTimerMap* gSecureTextTimers = 0;
|
| -
|
| -class SecureTextTimer final : public TimerBase {
|
| -public:
|
| - SecureTextTimer(RenderText* renderText)
|
| - : m_renderText(renderText)
|
| - , m_lastTypedCharacterOffset(-1)
|
| - {
|
| - }
|
| -
|
| - void restartWithNewText(unsigned lastTypedCharacterOffset)
|
| - {
|
| - m_lastTypedCharacterOffset = lastTypedCharacterOffset;
|
| - if (Settings* settings = m_renderText->document().settings())
|
| - startOneShot(settings->passwordEchoDurationInSeconds(), FROM_HERE);
|
| - }
|
| - void invalidate() { m_lastTypedCharacterOffset = -1; }
|
| - unsigned lastTypedCharacterOffset() { return m_lastTypedCharacterOffset; }
|
| -
|
| -private:
|
| - virtual void fired() override
|
| - {
|
| - ASSERT(gSecureTextTimers->contains(m_renderText));
|
| - m_renderText->setText(m_renderText->text().impl(), true /* forcing setting text as it may be masked later */);
|
| - }
|
| -
|
| - RenderText* m_renderText;
|
| - int m_lastTypedCharacterOffset;
|
| -};
|
| -
|
| -static void makeCapitalized(String* string, UChar previous)
|
| -{
|
| - if (string->isNull())
|
| - return;
|
| -
|
| - unsigned length = string->length();
|
| - const StringImpl& input = *string->impl();
|
| -
|
| - if (length >= std::numeric_limits<unsigned>::max())
|
| - CRASH();
|
| -
|
| - StringBuffer<UChar> stringWithPrevious(length + 1);
|
| - stringWithPrevious[0] = previous == noBreakSpace ? space : previous;
|
| - for (unsigned i = 1; i < length + 1; i++) {
|
| - // Replace   with a real space since ICU no longer treats   as a word separator.
|
| - if (input[i - 1] == noBreakSpace)
|
| - stringWithPrevious[i] = space;
|
| - else
|
| - stringWithPrevious[i] = input[i - 1];
|
| - }
|
| -
|
| - TextBreakIterator* boundary = wordBreakIterator(stringWithPrevious.characters(), length + 1);
|
| - if (!boundary)
|
| - return;
|
| -
|
| - StringBuilder result;
|
| - result.reserveCapacity(length);
|
| -
|
| - int32_t endOfWord;
|
| - int32_t startOfWord = boundary->first();
|
| - for (endOfWord = boundary->next(); endOfWord != TextBreakDone; startOfWord = endOfWord, endOfWord = boundary->next()) {
|
| - if (startOfWord) // Ignore first char of previous string
|
| - result.append(input[startOfWord - 1] == noBreakSpace ? noBreakSpace : toTitleCase(stringWithPrevious[startOfWord]));
|
| - for (int i = startOfWord + 1; i < endOfWord; i++)
|
| - result.append(input[i - 1]);
|
| - }
|
| -
|
| - *string = result.toString();
|
| -}
|
| -
|
| -RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str)
|
| - : LayoutObject(!node || node->isDocumentNode() ? 0 : node)
|
| - , m_hasTab(false)
|
| - , m_linesDirty(false)
|
| - , m_containsReversedText(false)
|
| - , m_knownToHaveNoOverflowAndNoFallbackFonts(false)
|
| - , m_minWidth(-1)
|
| - , m_maxWidth(-1)
|
| - , m_firstLineMinWidth(0)
|
| - , m_lastLineLineMinWidth(0)
|
| - , m_text(str)
|
| - , m_firstTextBox(0)
|
| - , m_lastTextBox(0)
|
| -{
|
| - ASSERT(m_text);
|
| - // FIXME: Some clients of RenderText (and subclasses) pass Document as node to create anonymous renderer.
|
| - // They should be switched to passing null and using setDocumentForAnonymous.
|
| - if (node && node->isDocumentNode())
|
| - setDocumentForAnonymous(toDocument(node));
|
| -
|
| - m_isAllASCII = m_text.containsOnlyASCII();
|
| - m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
|
| - setIsText();
|
| -
|
| - view()->frameView()->incrementVisuallyNonEmptyCharacterCount(m_text.length());
|
| -}
|
| -
|
| -#if ENABLE(ASSERT)
|
| -
|
| -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 LayoutStyle* oldStyle)
|
| -{
|
| - // There is no need to ever schedule paint invalidations 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.needsFullLayout()) {
|
| - setNeedsLayoutAndPrefWidthsRecalc();
|
| - m_knownToHaveNoOverflowAndNoFallbackFonts = false;
|
| - }
|
| -
|
| - const LayoutStyle& newStyle = styleRef();
|
| - ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE;
|
| - ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE;
|
| - if (oldTransform != newStyle.textTransform() || oldSecurity != newStyle.textSecurity())
|
| - transformText();
|
| -
|
| - // This is an optimization that kicks off font load before layout.
|
| - // In order to make it fast, we only check if the first character of the
|
| - // text is included in the unicode ranges of the fonts.
|
| - if (!text().containsOnlyWhitespace())
|
| - newStyle.font().willUseFontData(text().characterStartingAt(0));
|
| -}
|
| -
|
| -void RenderText::removeAndDestroyTextBoxes()
|
| -{
|
| - 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();
|
| -}
|
| -
|
| -void RenderText::willBeDestroyed()
|
| -{
|
| - if (SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->take(this) : 0)
|
| - delete secureTextTimer;
|
| -
|
| - removeAndDestroyTextBoxes();
|
| - LayoutObject::willBeDestroyed();
|
| -}
|
| -
|
| -void RenderText::extractTextBox(InlineTextBox* box)
|
| -{
|
| - checkConsistency();
|
| -
|
| - m_lastTextBox = box->prevTextBox();
|
| - if (box == m_firstTextBox)
|
| - m_firstTextBox = 0;
|
| - if (box->prevTextBox())
|
| - box->prevTextBox()->setNextTextBox(0);
|
| - box->setPreviousTextBox(0);
|
| - for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox())
|
| - curr->setExtracted();
|
| -
|
| - checkConsistency();
|
| -}
|
| -
|
| -void RenderText::attachTextBox(InlineTextBox* box)
|
| -{
|
| - checkConsistency();
|
| -
|
| - if (m_lastTextBox) {
|
| - m_lastTextBox->setNextTextBox(box);
|
| - box->setPreviousTextBox(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()->setPreviousTextBox(box->prevTextBox());
|
| - if (box->prevTextBox())
|
| - box->prevTextBox()->setNextTextBox(box->nextTextBox());
|
| -
|
| - checkConsistency();
|
| -}
|
| -
|
| -void RenderText::deleteTextBoxes()
|
| -{
|
| - if (firstTextBox()) {
|
| - InlineTextBox* next;
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = next) {
|
| - next = curr->nextTextBox();
|
| - curr->destroy();
|
| - }
|
| - m_firstTextBox = m_lastTextBox = 0;
|
| - }
|
| -}
|
| -
|
| -PassRefPtr<StringImpl> RenderText::originalText() const
|
| -{
|
| - Node* e = node();
|
| - return (e && e->isTextNode()) ? toText(e)->dataImpl() : 0;
|
| -}
|
| -
|
| -String RenderText::plainText() const
|
| -{
|
| - if (node())
|
| - return blink::plainText(rangeOfContents(node()).get());
|
| -
|
| - // FIXME: this is just a stopgap until TextIterator is adapted to support generated text.
|
| - StringBuilder plainTextBuilder;
|
| - for (InlineTextBox* textBox = firstTextBox(); textBox; textBox = textBox->nextTextBox()) {
|
| - String text = m_text.substring(textBox->start(), textBox->len()).simplifyWhiteSpace(WTF::DoNotStripWhiteSpace);
|
| - plainTextBuilder.append(text);
|
| - if (textBox->nextTextBox() && textBox->nextTextBox()->start() > textBox->end() && text.length() && !text.right(1).containsOnlyWhitespace())
|
| - plainTextBuilder.append(space);
|
| - }
|
| - return plainTextBuilder.toString();
|
| -}
|
| -
|
| -void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
|
| -{
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - rects.append(enclosingIntRect(FloatRect(FloatPoint(accumulatedOffset) + box->topLeft().toFloatPoint(), box->size().toFloatSize())));
|
| -}
|
| -
|
| -static FloatRect localQuadForTextBox(InlineTextBox* box, unsigned start, unsigned end, bool useSelectionHeight)
|
| -{
|
| - unsigned realEnd = std::min(box->end() + 1, end);
|
| - LayoutRect r = box->localSelectionRect(start, realEnd);
|
| - if (r.height()) {
|
| - if (!useSelectionHeight) {
|
| - // Change the height and y position (or width and x for vertical text)
|
| - // because selectionRect uses selection-specific values.
|
| - if (box->isHorizontal()) {
|
| - r.setHeight(box->height());
|
| - r.setY(box->y());
|
| - } else {
|
| - r.setWidth(box->width());
|
| - r.setX(box->x());
|
| - }
|
| - }
|
| - return FloatRect(r);
|
| - }
|
| - return FloatRect();
|
| -}
|
| -
|
| -void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed)
|
| -{
|
| - // 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 = std::min(start, static_cast<unsigned>(INT_MAX));
|
| - end = std::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) {
|
| - FloatRect r = box->calculateBoundaries().toFloatRect();
|
| - if (useSelectionHeight) {
|
| - LayoutRect selectionRect = box->localSelectionRect(start, end);
|
| - if (box->isHorizontal()) {
|
| - r.setHeight(selectionRect.height().toFloat());
|
| - r.setY(selectionRect.y().toFloat());
|
| - } else {
|
| - r.setWidth(selectionRect.width().toFloat());
|
| - r.setX(selectionRect.x().toFloat());
|
| - }
|
| - }
|
| - rects.append(localToAbsoluteQuad(r, 0, wasFixed).enclosingBoundingBox());
|
| - } else {
|
| - // FIXME: This code is wrong. It's converting local to absolute twice. http://webkit.org/b/65722
|
| - FloatRect rect = localQuadForTextBox(box, start, end, useSelectionHeight);
|
| - if (!rect.isZero())
|
| - rects.append(localToAbsoluteQuad(rect, 0, wasFixed).enclosingBoundingBox());
|
| - }
|
| - }
|
| -}
|
| -
|
| -static IntRect ellipsisRectForBox(InlineTextBox* box, unsigned startPos, unsigned endPos)
|
| -{
|
| - if (!box)
|
| - return IntRect();
|
| -
|
| - unsigned short truncation = box->truncation();
|
| - if (truncation == cNoTruncation)
|
| - return IntRect();
|
| -
|
| - IntRect rect;
|
| - if (EllipsisBox* ellipsis = box->root().ellipsisBox()) {
|
| - int ellipsisStartPosition = std::max<int>(startPos - box->start(), 0);
|
| - int ellipsisEndPosition = std::min<int>(endPos - box->start(), box->len());
|
| -
|
| - // The ellipsis should be considered to be selected if the end of
|
| - // the selection is past the beginning of the truncation and the
|
| - // beginning of the selection is before or at the beginning of the truncation.
|
| - if (ellipsisEndPosition >= truncation && ellipsisStartPosition <= truncation)
|
| - return ellipsis->selectionRect();
|
| - }
|
| -
|
| - return IntRect();
|
| -}
|
| -
|
| -void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed, ClippingOption option) const
|
| -{
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - FloatRect boundaries = box->calculateBoundaries().toFloatRect();
|
| -
|
| - // Shorten the width of this text box if it ends in an ellipsis.
|
| - // FIXME: ellipsisRectForBox should switch to return FloatRect soon with the subpixellayout branch.
|
| - IntRect ellipsisRect = (option == ClipToEllipsis) ? ellipsisRectForBox(box, 0, textLength()) : IntRect();
|
| - if (!ellipsisRect.isEmpty()) {
|
| - if (style()->isHorizontalWritingMode())
|
| - boundaries.setWidth(ellipsisRect.maxX() - boundaries.x());
|
| - else
|
| - boundaries.setHeight(ellipsisRect.maxY() - boundaries.y());
|
| - }
|
| - quads.append(localToAbsoluteQuad(boundaries, 0, wasFixed));
|
| - }
|
| -}
|
| -
|
| -void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
|
| -{
|
| - absoluteQuads(quads, wasFixed, NoClipping);
|
| -}
|
| -
|
| -void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed)
|
| -{
|
| - // 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 = std::min(start, static_cast<unsigned>(INT_MAX));
|
| - end = std::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) {
|
| - FloatRect r = box->calculateBoundaries().toFloatRect();
|
| - if (useSelectionHeight) {
|
| - LayoutRect selectionRect = box->localSelectionRect(start, end);
|
| - if (box->isHorizontal()) {
|
| - r.setHeight(selectionRect.height().toFloat());
|
| - r.setY(selectionRect.y().toFloat());
|
| - } else {
|
| - r.setWidth(selectionRect.width().toFloat());
|
| - r.setX(selectionRect.x().toFloat());
|
| - }
|
| - }
|
| - quads.append(localToAbsoluteQuad(r, 0, wasFixed));
|
| - } else {
|
| - FloatRect rect = localQuadForTextBox(box, start, end, useSelectionHeight);
|
| - if (!rect.isZero())
|
| - quads.append(localToAbsoluteQuad(rect, 0, wasFixed));
|
| - }
|
| - }
|
| -}
|
| -
|
| -enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart };
|
| -
|
| -static bool lineDirectionPointFitsInBox(int pointLineDirection, InlineTextBox* box, ShouldAffinityBeDownstream& shouldAffinityBeDownstream)
|
| -{
|
| - shouldAffinityBeDownstream = AlwaysDownstream;
|
| -
|
| - // 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
|
| - // except when box is the first box in the line
|
| - if (pointLineDirection <= box->logicalLeft()) {
|
| - shouldAffinityBeDownstream = !box->prevLeafChild() ? UpstreamIfPositionIsNotAtStart : AlwaysDownstream;
|
| - return true;
|
| - }
|
| -
|
| - // and the x coordinate is to the left of the right edge of this box
|
| - // check to see if position goes in this box
|
| - if (pointLineDirection < box->logicalRight()) {
|
| - shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
|
| - return true;
|
| - }
|
| -
|
| - // box is first on line
|
| - // and the x coordinate is to the left of the first text box left edge
|
| - if (!box->prevLeafChildIgnoringLineBreak() && pointLineDirection < box->logicalLeft())
|
| - return true;
|
| -
|
| - if (!box->nextLeafChildIgnoringLineBreak()) {
|
| - // 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
|
| - shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
|
| - return true;
|
| - }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -static PositionWithAffinity createPositionWithAffinityForBox(const InlineBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
|
| -{
|
| - EAffinity affinity = VP_DEFAULT_AFFINITY;
|
| - switch (shouldAffinityBeDownstream) {
|
| - case AlwaysDownstream:
|
| - affinity = DOWNSTREAM;
|
| - break;
|
| - case AlwaysUpstream:
|
| - affinity = VP_UPSTREAM_IF_POSSIBLE;
|
| - break;
|
| - case UpstreamIfPositionIsNotAtStart:
|
| - affinity = offset > box->caretMinOffset() ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM;
|
| - break;
|
| - }
|
| - int textStartOffset = box->renderer().isText() ? toRenderText(box->renderer()).textStartOffset() : 0;
|
| - return box->renderer().createPositionWithAffinity(offset + textStartOffset, affinity);
|
| -}
|
| -
|
| -static PositionWithAffinity createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(const InlineTextBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
|
| -{
|
| - ASSERT(box);
|
| - ASSERT(offset >= 0);
|
| -
|
| - if (offset && static_cast<unsigned>(offset) < box->len())
|
| - return createPositionWithAffinityForBox(box, box->start() + offset, shouldAffinityBeDownstream);
|
| -
|
| - bool positionIsAtStartOfBox = !offset;
|
| - if (positionIsAtStartOfBox == box->isLeftToRightDirection()) {
|
| - // offset is on the left edge
|
| -
|
| - const InlineBox* prevBox = box->prevLeafChildIgnoringLineBreak();
|
| - if ((prevBox && prevBox->bidiLevel() == box->bidiLevel())
|
| - || box->renderer().containingBlock()->style()->direction() == box->direction()) // FIXME: left on 12CBA
|
| - return createPositionWithAffinityForBox(box, box->caretLeftmostOffset(), shouldAffinityBeDownstream);
|
| -
|
| - if (prevBox && prevBox->bidiLevel() > box->bidiLevel()) {
|
| - // e.g. left of B in aDC12BAb
|
| - const InlineBox* leftmostBox;
|
| - do {
|
| - leftmostBox = prevBox;
|
| - prevBox = leftmostBox->prevLeafChildIgnoringLineBreak();
|
| - } while (prevBox && prevBox->bidiLevel() > box->bidiLevel());
|
| - return createPositionWithAffinityForBox(leftmostBox, leftmostBox->caretRightmostOffset(), shouldAffinityBeDownstream);
|
| - }
|
| -
|
| - if (!prevBox || prevBox->bidiLevel() < box->bidiLevel()) {
|
| - // e.g. left of D in aDC12BAb
|
| - const InlineBox* rightmostBox;
|
| - const InlineBox* nextBox = box;
|
| - do {
|
| - rightmostBox = nextBox;
|
| - nextBox = rightmostBox->nextLeafChildIgnoringLineBreak();
|
| - } while (nextBox && nextBox->bidiLevel() >= box->bidiLevel());
|
| - return createPositionWithAffinityForBox(rightmostBox,
|
| - box->isLeftToRightDirection() ? rightmostBox->caretMaxOffset() : rightmostBox->caretMinOffset(), shouldAffinityBeDownstream);
|
| - }
|
| -
|
| - return createPositionWithAffinityForBox(box, box->caretRightmostOffset(), shouldAffinityBeDownstream);
|
| - }
|
| -
|
| - const InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak();
|
| - if ((nextBox && nextBox->bidiLevel() == box->bidiLevel())
|
| - || box->renderer().containingBlock()->style()->direction() == box->direction())
|
| - return createPositionWithAffinityForBox(box, box->caretRightmostOffset(), shouldAffinityBeDownstream);
|
| -
|
| - // offset is on the right edge
|
| - if (nextBox && nextBox->bidiLevel() > box->bidiLevel()) {
|
| - // e.g. right of C in aDC12BAb
|
| - const InlineBox* rightmostBox;
|
| - do {
|
| - rightmostBox = nextBox;
|
| - nextBox = rightmostBox->nextLeafChildIgnoringLineBreak();
|
| - } while (nextBox && nextBox->bidiLevel() > box->bidiLevel());
|
| - return createPositionWithAffinityForBox(rightmostBox, rightmostBox->caretLeftmostOffset(), shouldAffinityBeDownstream);
|
| - }
|
| -
|
| - if (!nextBox || nextBox->bidiLevel() < box->bidiLevel()) {
|
| - // e.g. right of A in aDC12BAb
|
| - const InlineBox* leftmostBox;
|
| - const InlineBox* prevBox = box;
|
| - do {
|
| - leftmostBox = prevBox;
|
| - prevBox = leftmostBox->prevLeafChildIgnoringLineBreak();
|
| - } while (prevBox && prevBox->bidiLevel() >= box->bidiLevel());
|
| - return createPositionWithAffinityForBox(leftmostBox,
|
| - box->isLeftToRightDirection() ? leftmostBox->caretMinOffset() : leftmostBox->caretMaxOffset(), shouldAffinityBeDownstream);
|
| - }
|
| -
|
| - return createPositionWithAffinityForBox(box, box->caretLeftmostOffset(), shouldAffinityBeDownstream);
|
| -}
|
| -
|
| -PositionWithAffinity RenderText::positionForPoint(const LayoutPoint& point)
|
| -{
|
| - if (!firstTextBox() || textLength() == 0)
|
| - return createPositionWithAffinity(0, DOWNSTREAM);
|
| -
|
| - LayoutUnit pointLineDirection = firstTextBox()->isHorizontal() ? point.x() : point.y();
|
| - LayoutUnit pointBlockDirection = firstTextBox()->isHorizontal() ? point.y() : point.x();
|
| - bool blocksAreFlipped = style()->isFlippedBlocksWritingMode();
|
| -
|
| - InlineTextBox* lastBox = 0;
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - if (box->isLineBreak() && !box->prevLeafChild() && box->nextLeafChild() && !box->nextLeafChild()->isLineBreak())
|
| - box = box->nextTextBox();
|
| -
|
| - RootInlineBox& rootBox = box->root();
|
| - LayoutUnit top = std::min(rootBox.selectionTop(), rootBox.lineTop());
|
| - if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirection == top)) {
|
| - LayoutUnit bottom = rootBox.selectionBottom();
|
| - if (rootBox.nextRootBox())
|
| - bottom = std::min(bottom, rootBox.nextRootBox()->lineTop());
|
| -
|
| - if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockDirection == bottom)) {
|
| - ShouldAffinityBeDownstream shouldAffinityBeDownstream;
|
| - if (lineDirectionPointFitsInBox(pointLineDirection, box, shouldAffinityBeDownstream))
|
| - return createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(box, box->offsetForPosition(pointLineDirection.toFloat()), shouldAffinityBeDownstream);
|
| - }
|
| - }
|
| - lastBox = box;
|
| - }
|
| -
|
| - if (lastBox) {
|
| - ShouldAffinityBeDownstream shouldAffinityBeDownstream;
|
| - lineDirectionPointFitsInBox(pointLineDirection, lastBox, shouldAffinityBeDownstream);
|
| - return createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(lastBox, lastBox->offsetForPosition(pointLineDirection.toFloat()) + lastBox->start(), shouldAffinityBeDownstream);
|
| - }
|
| - return createPositionWithAffinity(0, DOWNSTREAM);
|
| -}
|
| -
|
| -LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, LayoutUnit* extraWidthToEndOfLine)
|
| -{
|
| - if (!inlineBox)
|
| - return LayoutRect();
|
| -
|
| - ASSERT(inlineBox->isInlineTextBox());
|
| - if (!inlineBox->isInlineTextBox())
|
| - return LayoutRect();
|
| -
|
| - InlineTextBox* box = toInlineTextBox(inlineBox);
|
| -
|
| - int height = box->root().selectionHeight();
|
| - int top = box->root().selectionTop();
|
| -
|
| - // Go ahead and round left to snap it to the nearest pixel.
|
| - float left = box->positionForOffset(caretOffset);
|
| -
|
| - // Distribute the caret's width to either side of the offset.
|
| - int caretWidthLeftOfOffset = caretWidth / 2;
|
| - left -= caretWidthLeftOfOffset;
|
| - int caretWidthRightOfOffset = caretWidth - caretWidthLeftOfOffset;
|
| -
|
| - left = roundf(left);
|
| -
|
| - float rootLeft = box->root().logicalLeft();
|
| - float rootRight = box->root().logicalRight();
|
| -
|
| - // 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().logicalWidth() + rootLeft) - (left + 1);
|
| -
|
| - LayoutBlock* cb = containingBlock();
|
| - const LayoutStyle& cbStyle = cb->styleRef();
|
| -
|
| - float leftEdge;
|
| - float rightEdge;
|
| - leftEdge = std::min<float>(0, rootLeft);
|
| - rightEdge = std::max<float>(cb->logicalWidth().toFloat(), rootRight);
|
| -
|
| - bool rightAligned = false;
|
| - switch (cbStyle.textAlign()) {
|
| - case RIGHT:
|
| - case WEBKIT_RIGHT:
|
| - rightAligned = true;
|
| - break;
|
| - case LEFT:
|
| - case WEBKIT_LEFT:
|
| - case CENTER:
|
| - case WEBKIT_CENTER:
|
| - break;
|
| - case JUSTIFY:
|
| - case TASTART:
|
| - rightAligned = !cbStyle.isLeftToRightDirection();
|
| - break;
|
| - case TAEND:
|
| - rightAligned = cbStyle.isLeftToRightDirection();
|
| - break;
|
| - }
|
| -
|
| - // for dir=auto, use inlineBoxBidiLevel() to test the correct direction for the cursor.
|
| - if (rightAligned && (node() && node()->selfOrAncestorHasDirAutoAttribute())) {
|
| - if (inlineBox->bidiLevel()%2 != 1)
|
| - rightAligned = false;
|
| - }
|
| -
|
| - if (rightAligned) {
|
| - left = std::max(left, leftEdge);
|
| - left = std::min(left, rootRight - caretWidth);
|
| - } else {
|
| - left = std::min(left, rightEdge - caretWidthRightOfOffset);
|
| - left = std::max(left, rootLeft);
|
| - }
|
| -
|
| - return LayoutRect(style()->isHorizontalWritingMode() ? IntRect(left, top, caretWidth, height) : IntRect(top, left, height, caretWidth));
|
| -}
|
| -
|
| -ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len, float xPos, TextDirection textDirection, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
|
| -{
|
| - if (style()->hasTextCombine() && isCombineText()) {
|
| - const RenderCombineText* combineText = toRenderCombineText(this);
|
| - if (combineText->isCombined())
|
| - return combineText->combinedTextWidth(f);
|
| - }
|
| -
|
| - if (f.isFixedPitch() && f.fontDescription().variant() == FontVariantNormal && m_isAllASCII && (!glyphOverflow || !glyphOverflow->computeBounds)) {
|
| - bool missingGlyph = false;
|
| - float monospaceCharacterWidth = f.spaceWidth();
|
| - float w = 0;
|
| - bool isSpace;
|
| - ASSERT(m_text);
|
| - StringImpl& text = *m_text.impl();
|
| - const LayoutStyle& layoutStyle = styleRef();
|
| - for (int i = start; i < start + len; i++) {
|
| - char c = text[i];
|
| - // If glyph is not present in primary font then we cannot calculate width based on primary
|
| - // font property, we need to call "width" method of Font Object.
|
| - if (!f.primaryFontHasGlyphForCharacter(text[i])) {
|
| - missingGlyph = true;
|
| - break;
|
| - }
|
| - if (c <= space) {
|
| - if (c == space || c == newlineCharacter) {
|
| - w += monospaceCharacterWidth;
|
| - isSpace = true;
|
| - } else if (c == characterTabulation) {
|
| - if (layoutStyle.collapseWhiteSpace()) {
|
| - w += monospaceCharacterWidth;
|
| - isSpace = true;
|
| - } else {
|
| - w += f.tabWidth(layoutStyle.tabSize(), xPos + w);
|
| - isSpace = false;
|
| - }
|
| - } else
|
| - isSpace = false;
|
| - } else {
|
| - w += monospaceCharacterWidth;
|
| - isSpace = false;
|
| - }
|
| - if (isSpace && i > start)
|
| - w += f.fontDescription().wordSpacing();
|
| - }
|
| - if (!missingGlyph)
|
| - return w;
|
| - }
|
| -
|
| - TextRun run = constructTextRun(const_cast<RenderText*>(this), f, this, start, len, styleRef(), textDirection);
|
| - run.setCharactersLength(textLength() - start);
|
| - ASSERT(run.charactersLength() >= run.length());
|
| - run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun::ForceComplex);
|
| - run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
|
| - run.setXPos(xPos);
|
| - return f.width(run, fallbackFonts, glyphOverflow);
|
| -}
|
| -
|
| -void RenderText::trimmedPrefWidths(FloatWillBeLayoutUnit leadWidth,
|
| - FloatWillBeLayoutUnit& firstLineMinWidth, bool& hasBreakableStart,
|
| - FloatWillBeLayoutUnit& lastLineMinWidth, bool& hasBreakableEnd,
|
| - bool& hasBreakableChar, bool& hasBreak,
|
| - FloatWillBeLayoutUnit& firstLineMaxWidth, FloatWillBeLayoutUnit& lastLineMaxWidth,
|
| - FloatWillBeLayoutUnit& minWidth, FloatWillBeLayoutUnit& maxWidth, bool& stripFrontSpaces,
|
| - TextDirection direction)
|
| -{
|
| - bool collapseWhiteSpace = style()->collapseWhiteSpace();
|
| - if (!collapseWhiteSpace)
|
| - stripFrontSpaces = false;
|
| -
|
| - if (m_hasTab || preferredLogicalWidthsDirty())
|
| - computePreferredLogicalWidths(leadWidth);
|
| -
|
| - hasBreakableStart = !stripFrontSpaces && m_hasBreakableStart;
|
| - hasBreakableEnd = m_hasBreakableEnd;
|
| -
|
| - int len = textLength();
|
| -
|
| - if (!len || (stripFrontSpaces && text().impl()->containsOnlyWhitespace())) {
|
| - firstLineMinWidth = FloatWillBeLayoutUnit();
|
| - lastLineMinWidth = FloatWillBeLayoutUnit();
|
| - firstLineMaxWidth = FloatWillBeLayoutUnit();
|
| - lastLineMaxWidth = FloatWillBeLayoutUnit();
|
| - minWidth = FloatWillBeLayoutUnit();
|
| - maxWidth = FloatWillBeLayoutUnit();
|
| - hasBreak = false;
|
| - return;
|
| - }
|
| -
|
| - minWidth = m_minWidth;
|
| - maxWidth = m_maxWidth;
|
| -
|
| - firstLineMinWidth = m_firstLineMinWidth;
|
| - lastLineMinWidth = m_lastLineLineMinWidth;
|
| -
|
| - hasBreakableChar = m_hasBreakableChar;
|
| - hasBreak = m_hasBreak;
|
| -
|
| - ASSERT(m_text);
|
| - StringImpl& text = *m_text.impl();
|
| - if (text[0] == space || (text[0] == newlineCharacter && !style()->preserveNewline()) || text[0] == characterTabulation) {
|
| - const Font& font = style()->font(); // FIXME: This ignores first-line.
|
| - if (stripFrontSpaces) {
|
| - const UChar spaceChar = space;
|
| - TextRun run = constructTextRun(this, font, &spaceChar, 1, styleRef(), direction);
|
| - run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun::ForceComplex);
|
| - float spaceWidth = font.width(run);
|
| - maxWidth -= spaceWidth;
|
| - } else {
|
| - maxWidth += font.fontDescription().wordSpacing();
|
| - }
|
| - }
|
| -
|
| - stripFrontSpaces = collapseWhiteSpace && m_hasEndWhiteSpace;
|
| -
|
| - if (!style()->autoWrap() || minWidth > maxWidth)
|
| - minWidth = maxWidth;
|
| -
|
| - // 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;
|
| - firstLineMaxWidth = maxWidth;
|
| - lastLineMaxWidth = maxWidth;
|
| - for (int i = 0; i < len; i++) {
|
| - int linelen = 0;
|
| - while (i + linelen < len && text[i + linelen] != newlineCharacter)
|
| - linelen++;
|
| -
|
| - if (linelen) {
|
| - lastLineMaxWidth = widthFromCache(f, i, linelen, leadWidth + lastLineMaxWidth, direction, 0, 0);
|
| - if (firstLine) {
|
| - firstLine = false;
|
| - leadWidth = FloatWillBeLayoutUnit();
|
| - firstLineMaxWidth = lastLineMaxWidth;
|
| - }
|
| - i += linelen;
|
| - } else if (firstLine) {
|
| - firstLineMaxWidth = FloatWillBeLayoutUnit();
|
| - firstLine = false;
|
| - leadWidth = FloatWillBeLayoutUnit();
|
| - }
|
| -
|
| - 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>
|
| - lastLineMaxWidth = FloatWillBeLayoutUnit();
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -float RenderText::minLogicalWidth() const
|
| -{
|
| - if (preferredLogicalWidthsDirty())
|
| - const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
|
| -
|
| - return m_minWidth;
|
| -}
|
| -
|
| -float RenderText::maxLogicalWidth() const
|
| -{
|
| - if (preferredLogicalWidthsDirty())
|
| - const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
|
| -
|
| - return m_maxWidth;
|
| -}
|
| -
|
| -void RenderText::computePreferredLogicalWidths(float leadWidth)
|
| -{
|
| - HashSet<const SimpleFontData*> fallbackFonts;
|
| - GlyphOverflow glyphOverflow;
|
| - computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow);
|
| -
|
| - // We shouldn't change our mind once we "know".
|
| - ASSERT(!m_knownToHaveNoOverflowAndNoFallbackFonts || (fallbackFonts.isEmpty() && glyphOverflow.isZero()));
|
| - m_knownToHaveNoOverflowAndNoFallbackFonts = fallbackFonts.isEmpty() && glyphOverflow.isZero();
|
| -}
|
| -
|
| -static inline float hyphenWidth(RenderText* renderer, const Font& font, TextDirection direction)
|
| -{
|
| - const LayoutStyle& style = renderer->styleRef();
|
| - return font.width(constructTextRun(renderer, font, style.hyphenString().string(), style, direction));
|
| -}
|
| -
|
| -void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const SimpleFontData*>& fallbackFonts, GlyphOverflow& glyphOverflow)
|
| -{
|
| - ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts);
|
| -
|
| - m_minWidth = 0;
|
| - m_maxWidth = 0;
|
| - m_firstLineMinWidth = 0;
|
| - m_lastLineLineMinWidth = 0;
|
| -
|
| - if (isBR())
|
| - return;
|
| -
|
| - float currMinWidth = 0;
|
| - float currMaxWidth = 0;
|
| - m_hasBreakableChar = false;
|
| - m_hasBreak = false;
|
| - m_hasTab = false;
|
| - m_hasBreakableStart = false;
|
| - m_hasBreakableEnd = false;
|
| - m_hasEndWhiteSpace = false;
|
| -
|
| - const LayoutStyle& styleToUse = styleRef();
|
| - const Font& f = styleToUse.font(); // FIXME: This ignores first-line.
|
| - float wordSpacing = styleToUse.wordSpacing();
|
| - int len = textLength();
|
| - LazyLineBreakIterator breakIterator(m_text, styleToUse.locale());
|
| - bool needsWordSpacing = false;
|
| - bool ignoringSpaces = false;
|
| - bool isSpace = false;
|
| - bool firstWord = true;
|
| - bool firstLine = true;
|
| - int nextBreakable = -1;
|
| - int lastWordBoundary = 0;
|
| - float cachedWordTrailingSpaceWidth[2] = { 0, 0 }; // LTR, RTL
|
| -
|
| - int firstGlyphLeftOverflow = -1;
|
| -
|
| - bool breakAll = (styleToUse.wordBreak() == BreakAllWordBreak || styleToUse.wordBreak() == BreakWordBreak) && styleToUse.autoWrap();
|
| -
|
| - TextRun textRun(text());
|
| - BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver;
|
| - BidiCharacterRun* run;
|
| - TextDirection textDirection = styleToUse.direction();
|
| - if (isOverride(styleToUse.unicodeBidi())) {
|
| - run = 0;
|
| - } else {
|
| - BidiStatus status(textDirection, false);
|
| - bidiResolver.setStatus(status);
|
| - bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&textRun, 0));
|
| - bool hardLineBreak = false;
|
| - bool reorderRuns = false;
|
| - bidiResolver.createBidiRunsForLine(TextRunIterator(&textRun, textRun.length()), NoVisualOverride, hardLineBreak, reorderRuns);
|
| - BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs();
|
| - run = bidiRuns.firstRun();
|
| - }
|
| -
|
| - for (int i = 0; i < len; i++) {
|
| - UChar c = uncheckedCharacterAt(i);
|
| -
|
| - if (run) {
|
| - // Treat adjacent runs with the same resolved directionality
|
| - // (TextDirection as opposed to WTF::Unicode::Direction) as belonging
|
| - // to the same run to avoid breaking unnecessarily.
|
| - while (i >= run->stop() || (run->next() && run->next()->direction() == run->direction()))
|
| - run = run->next();
|
| -
|
| - ASSERT(run);
|
| - ASSERT(i <= run->stop());
|
| - textDirection = run->direction();
|
| - }
|
| -
|
| - bool previousCharacterIsSpace = isSpace;
|
| - bool isNewline = false;
|
| - if (c == newlineCharacter) {
|
| - if (styleToUse.preserveNewline()) {
|
| - m_hasBreak = true;
|
| - isNewline = true;
|
| - isSpace = false;
|
| - } else
|
| - isSpace = true;
|
| - } else if (c == characterTabulation) {
|
| - if (!styleToUse.collapseWhiteSpace()) {
|
| - m_hasTab = true;
|
| - isSpace = false;
|
| - } else
|
| - isSpace = true;
|
| - } else {
|
| - isSpace = c == space;
|
| - }
|
| -
|
| - bool isBreakableLocation = isNewline || (isSpace && styleToUse.autoWrap());
|
| - if (!i)
|
| - m_hasBreakableStart = isBreakableLocation;
|
| - if (i == len - 1) {
|
| - m_hasBreakableEnd = isBreakableLocation;
|
| - m_hasEndWhiteSpace = isNewline || isSpace;
|
| - }
|
| -
|
| - if (!ignoringSpaces && styleToUse.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, textDirection, &fallbackFonts, &glyphOverflow);
|
| - if (firstGlyphLeftOverflow < 0)
|
| - firstGlyphLeftOverflow = glyphOverflow.left;
|
| - lastWordBoundary = i + 1;
|
| - continue;
|
| - }
|
| -
|
| - bool hasBreak = breakIterator.isBreakable(i, nextBreakable, breakAll ? LineBreakType::BreakAll : LineBreakType::Normal);
|
| - bool betweenWords = true;
|
| - int j = i;
|
| - while (c != newlineCharacter && c != space && c != characterTabulation && (c != softHyphen)) {
|
| - j++;
|
| - if (j == len)
|
| - break;
|
| - c = uncheckedCharacterAt(j);
|
| - if (breakIterator.isBreakable(j, nextBreakable) && characterAt(j - 1) != softHyphen)
|
| - break;
|
| - if (breakAll) {
|
| - betweenWords = false;
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // Terminate word boundary at bidi run boundary.
|
| - if (run)
|
| - j = std::min(j, run->stop() + 1);
|
| - int wordLen = j - i;
|
| - if (wordLen) {
|
| - bool isSpace = (j < len) && c == space;
|
| -
|
| - // Non-zero only when kerning is enabled, in which case we measure words with their trailing
|
| - // space, then subtract its width.
|
| - float wordTrailingSpaceWidth = 0;
|
| - if (isSpace && (f.fontDescription().typesettingFeatures() & Kerning)) {
|
| - ASSERT(textDirection >=0 && textDirection <= 1);
|
| - if (!cachedWordTrailingSpaceWidth[textDirection])
|
| - cachedWordTrailingSpaceWidth[textDirection] = f.width(constructTextRun(this, f, &space, 1, styleToUse, textDirection)) + wordSpacing;
|
| - wordTrailingSpaceWidth = cachedWordTrailingSpaceWidth[textDirection];
|
| - }
|
| -
|
| - float w;
|
| - if (wordTrailingSpaceWidth && isSpace)
|
| - w = widthFromCache(f, i, wordLen + 1, leadWidth + currMaxWidth, textDirection, &fallbackFonts, &glyphOverflow) - wordTrailingSpaceWidth;
|
| - else {
|
| - w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth, textDirection, &fallbackFonts, &glyphOverflow);
|
| - if (c == softHyphen)
|
| - currMinWidth += hyphenWidth(this, f, textDirection);
|
| - }
|
| -
|
| - if (firstGlyphLeftOverflow < 0)
|
| - firstGlyphLeftOverflow = glyphOverflow.left;
|
| - currMinWidth += w;
|
| - if (betweenWords) {
|
| - if (lastWordBoundary == i)
|
| - currMaxWidth += w;
|
| - else
|
| - currMaxWidth += widthFromCache(f, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, textDirection, &fallbackFonts, &glyphOverflow);
|
| - lastWordBoundary = j;
|
| - }
|
| -
|
| - bool isCollapsibleWhiteSpace = (j < len) && styleToUse.isCollapsibleWhiteSpace(c);
|
| - if (j < len && styleToUse.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_firstLineMinWidth = hasBreak ? 0 : currMinWidth;
|
| - }
|
| - m_lastLineLineMinWidth = currMinWidth;
|
| -
|
| - 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 (!styleToUse.autoWrap())
|
| - m_firstLineMinWidth = currMaxWidth;
|
| - }
|
| -
|
| - if (currMaxWidth > m_maxWidth)
|
| - m_maxWidth = currMaxWidth;
|
| - currMaxWidth = 0;
|
| - } else {
|
| - TextRun run = constructTextRun(this, f, this, i, 1, styleToUse, textDirection);
|
| - run.setCharactersLength(len - i);
|
| - run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun::ForceComplex);
|
| - ASSERT(run.charactersLength() >= run.length());
|
| - run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
|
| - run.setXPos(leadWidth + currMaxWidth);
|
| -
|
| - currMaxWidth += f.width(run);
|
| - glyphOverflow.right = 0;
|
| - needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len - 1;
|
| - }
|
| - ASSERT(lastWordBoundary == i);
|
| - lastWordBoundary++;
|
| - }
|
| - }
|
| - if (run)
|
| - bidiResolver.runs().deleteRuns();
|
| -
|
| - if (firstGlyphLeftOverflow > 0)
|
| - glyphOverflow.left = firstGlyphLeftOverflow;
|
| -
|
| - if ((needsWordSpacing && len > 1) || (ignoringSpaces && !firstWord))
|
| - currMaxWidth += wordSpacing;
|
| -
|
| - m_minWidth = std::max(currMinWidth, m_minWidth);
|
| - m_maxWidth = std::max(currMaxWidth, m_maxWidth);
|
| -
|
| - if (!styleToUse.autoWrap())
|
| - m_minWidth = m_maxWidth;
|
| -
|
| - if (styleToUse.whiteSpace() == PRE) {
|
| - if (firstLine)
|
| - m_firstLineMinWidth = m_maxWidth;
|
| - m_lastLineLineMinWidth = currMaxWidth;
|
| - }
|
| -
|
| - clearPreferredLogicalWidthsDirty();
|
| -}
|
| -
|
| -bool RenderText::isAllCollapsibleWhitespace() const
|
| -{
|
| - unsigned length = textLength();
|
| - if (is8Bit()) {
|
| - for (unsigned i = 0; i < length; ++i) {
|
| - if (!style()->isCollapsibleWhiteSpace(characters8()[i]))
|
| - return false;
|
| - }
|
| - return true;
|
| - }
|
| - for (unsigned i = 0; i < length; ++i) {
|
| - if (!style()->isCollapsibleWhiteSpace(characters16()[i]))
|
| - return false;
|
| - }
|
| - return true;
|
| -}
|
| -
|
| -bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
|
| -{
|
| - ASSERT(m_text);
|
| - StringImpl& text = *m_text.impl();
|
| - unsigned currPos;
|
| - for (currPos = from;
|
| - currPos < from + len && (text[currPos] == newlineCharacter || text[currPos] == space || text[currPos] == characterTabulation);
|
| - currPos++) { }
|
| - return currPos >= (from + len);
|
| -}
|
| -
|
| -FloatPoint RenderText::firstRunOrigin() const
|
| -{
|
| - return IntPoint(firstRunX(), firstRunY());
|
| -}
|
| -
|
| -float RenderText::firstRunX() const
|
| -{
|
| - return m_firstTextBox ? m_firstTextBox->x().toFloat() : 0;
|
| -}
|
| -
|
| -float RenderText::firstRunY() const
|
| -{
|
| - return m_firstTextBox ? m_firstTextBox->y().toFloat() : 0;
|
| -}
|
| -
|
| -void RenderText::setSelectionState(SelectionState state)
|
| -{
|
| - LayoutObject::setSelectionState(state);
|
| -
|
| - if (canUpdateSelectionOnRootLineBoxes()) {
|
| - 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 && startPos == endPos)
|
| - startPos = endPos - 1;
|
| - } else if (selectionState() == SelectionEnd)
|
| - startPos = 0;
|
| -
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - if (box->isSelected(startPos, endPos)) {
|
| - box->root().setHasSelectedChildren(true);
|
| - }
|
| - }
|
| - } else {
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - box->root().setHasSelectedChildren(state == SelectionInside);
|
| - }
|
| - }
|
| - }
|
| -
|
| - // The containing block can be null in case of an orphaned tree.
|
| - LayoutBlock* containingBlock = this->containingBlock();
|
| - if (containingBlock && !containingBlock->isLayoutView())
|
| - containingBlock->setSelectionState(state);
|
| -}
|
| -
|
| -void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset, unsigned len, bool force)
|
| -{
|
| - if (!force && equal(m_text.impl(), text.get()))
|
| - return;
|
| -
|
| - 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()) {
|
| - // FIXME: This shouldn't rely on the end of a dirty line box. See https://bugs.webkit.org/show_bug.cgi?id=97264
|
| - // 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;
|
| - // 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;
|
| - } else if (lastTextBox()) {
|
| - ASSERT(!lastRootBox);
|
| - firstRootBox = &lastTextBox()->root();
|
| - firstRootBox->markDirty();
|
| - dirtiedLines = true;
|
| - }
|
| - for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr = curr->nextRootBox()) {
|
| - if (curr->lineBreakObj() == this && curr->lineBreakPos() > end)
|
| - curr->setLineBreakPos(clampTo<int>(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 || dirtiedLines);
|
| -}
|
| -
|
| -void RenderText::transformText()
|
| -{
|
| - if (RefPtr<StringImpl> textToTransform = originalText())
|
| - setText(textToTransform.release(), true);
|
| -}
|
| -
|
| -static inline bool isInlineFlowOrEmptyText(const LayoutObject* o)
|
| -{
|
| - if (o->isLayoutInline())
|
| - return true;
|
| - if (!o->isText())
|
| - return false;
|
| - return toRenderText(o)->text().isEmpty();
|
| -}
|
| -
|
| -UChar RenderText::previousCharacter() const
|
| -{
|
| - // find previous text renderer if one exists
|
| - const LayoutObject* previousText = previousInPreOrder();
|
| - for (; previousText; previousText = previousText->previousInPreOrder())
|
| - if (!isInlineFlowOrEmptyText(previousText))
|
| - break;
|
| - UChar prev = space;
|
| - if (previousText && previousText->isText())
|
| - if (StringImpl* previousString = toRenderText(previousText)->text().impl())
|
| - prev = (*previousString)[previousString->length() - 1];
|
| - return prev;
|
| -}
|
| -
|
| -void RenderText::addLayerHitTestRects(LayerHitTestRects&, const Layer* currentLayer, const LayoutPoint& layerOffset, const LayoutRect& containerRect) const
|
| -{
|
| - // Text nodes aren't event targets, so don't descend any further.
|
| -}
|
| -
|
| -void applyTextTransform(const LayoutStyle* style, String& text, UChar previousCharacter)
|
| -{
|
| - if (!style)
|
| - return;
|
| -
|
| - switch (style->textTransform()) {
|
| - case TTNONE:
|
| - break;
|
| - case CAPITALIZE:
|
| - makeCapitalized(&text, previousCharacter);
|
| - break;
|
| - case UPPERCASE:
|
| - text = text.upper(style->locale());
|
| - break;
|
| - case LOWERCASE:
|
| - text = text.lower(style->locale());
|
| - break;
|
| - }
|
| -}
|
| -
|
| -void RenderText::setTextInternal(PassRefPtr<StringImpl> text)
|
| -{
|
| - ASSERT(text);
|
| - m_text = text;
|
| -
|
| - if (style()) {
|
| - applyTextTransform(style(), m_text, previousCharacter());
|
| -
|
| - // We use the same characters here as for list markers.
|
| - // See the listMarkerText function in LayoutListMarker.cpp.
|
| - switch (style()->textSecurity()) {
|
| - case TSNONE:
|
| - break;
|
| - case TSCIRCLE:
|
| - secureText(whiteBullet);
|
| - break;
|
| - case TSDISC:
|
| - secureText(bullet);
|
| - break;
|
| - case TSSQUARE:
|
| - secureText(blackSquare);
|
| - }
|
| - }
|
| -
|
| - ASSERT(m_text);
|
| - ASSERT(!isBR() || (textLength() == 1 && m_text[0] == newlineCharacter));
|
| -
|
| - m_isAllASCII = m_text.containsOnlyASCII();
|
| - m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
|
| -}
|
| -
|
| -void RenderText::secureText(UChar mask)
|
| -{
|
| - if (!m_text.length())
|
| - return;
|
| -
|
| - int lastTypedCharacterOffsetToReveal = -1;
|
| - UChar revealedText;
|
| - SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->get(this) : 0;
|
| - if (secureTextTimer && secureTextTimer->isActive()) {
|
| - lastTypedCharacterOffsetToReveal = secureTextTimer->lastTypedCharacterOffset();
|
| - if (lastTypedCharacterOffsetToReveal >= 0)
|
| - revealedText = m_text[lastTypedCharacterOffsetToReveal];
|
| - }
|
| -
|
| - m_text.fill(mask);
|
| - if (lastTypedCharacterOffsetToReveal >= 0) {
|
| - m_text.replace(lastTypedCharacterOffsetToReveal, 1, String(&revealedText, 1));
|
| - // m_text may be updated later before timer fires. We invalidate the lastTypedCharacterOffset to avoid inconsistency.
|
| - secureTextTimer->invalidate();
|
| - }
|
| -}
|
| -
|
| -void RenderText::setText(PassRefPtr<StringImpl> text, bool force)
|
| -{
|
| - ASSERT(text);
|
| -
|
| - if (!force && equal(m_text.impl(), text.get()))
|
| - return;
|
| -
|
| - setTextInternal(text);
|
| - // If preferredLogicalWidthsDirty() of an orphan child is true, LayoutObjectChildList::
|
| - // insertChildNode() fails to set true to owner. To avoid that, we call
|
| - // setNeedsLayoutAndPrefWidthsRecalc() only if this RenderText has parent.
|
| - if (parent())
|
| - setNeedsLayoutAndPrefWidthsRecalc();
|
| - m_knownToHaveNoOverflowAndNoFallbackFonts = false;
|
| -
|
| - if (AXObjectCache* cache = document().existingAXObjectCache())
|
| - cache->textChanged(this);
|
| -}
|
| -
|
| -void RenderText::dirtyOrDeleteLineBoxesIfNeeded(bool fullLayout)
|
| -{
|
| - if (fullLayout)
|
| - deleteTextBoxes();
|
| - else if (!m_linesDirty)
|
| - dirtyLineBoxes();
|
| - m_linesDirty = false;
|
| -}
|
| -
|
| -void RenderText::dirtyLineBoxes()
|
| -{
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - box->dirtyLineBoxes();
|
| - m_linesDirty = false;
|
| -}
|
| -
|
| -InlineTextBox* RenderText::createTextBox(int start, unsigned short length)
|
| -{
|
| - return new InlineTextBox(*this, start, length);
|
| -}
|
| -
|
| -InlineTextBox* RenderText::createInlineTextBox(int start, unsigned short length)
|
| -{
|
| - InlineTextBox* textBox = createTextBox(start, length);
|
| - if (!m_firstTextBox)
|
| - m_firstTextBox = m_lastTextBox = textBox;
|
| - else {
|
| - m_lastTextBox->setNextTextBox(textBox);
|
| - textBox->setPreviousTextBox(m_lastTextBox);
|
| - m_lastTextBox = textBox;
|
| - }
|
| - return textBox;
|
| -}
|
| -
|
| -void RenderText::positionLineBox(InlineBox* box)
|
| -{
|
| - InlineTextBox* s = toInlineTextBox(box);
|
| -
|
| - // FIXME: should not be needed!!!
|
| - if (!s->len()) {
|
| - // We want the box to be destroyed.
|
| - s->remove(DontMarkLineBoxes);
|
| - if (m_firstTextBox == s)
|
| - m_firstTextBox = s->nextTextBox();
|
| - else
|
| - s->prevTextBox()->setNextTextBox(s->nextTextBox());
|
| - if (m_lastTextBox == s)
|
| - m_lastTextBox = s->prevTextBox();
|
| - else
|
| - s->nextTextBox()->setPreviousTextBox(s->prevTextBox());
|
| - s->destroy();
|
| - return;
|
| - }
|
| -
|
| - m_containsReversedText |= !s->isLeftToRightDirection();
|
| -}
|
| -
|
| -float RenderText::width(unsigned from, unsigned len, float xPos, TextDirection textDirection, bool firstLine, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
|
| -{
|
| - if (from >= textLength())
|
| - return 0;
|
| -
|
| - if (from + len > textLength())
|
| - len = textLength() - from;
|
| -
|
| - return width(from, len, style(firstLine)->font(), xPos, textDirection, fallbackFonts, glyphOverflow);
|
| -}
|
| -
|
| -float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos, TextDirection textDirection, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
|
| -{
|
| - ASSERT(from + len <= textLength());
|
| - if (!textLength())
|
| - return 0;
|
| -
|
| - float w;
|
| - if (&f == &style()->font()) {
|
| - if (!style()->preserveNewline() && !from && len == textLength() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
|
| - if (fallbackFonts) {
|
| - ASSERT(glyphOverflow);
|
| - if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts) {
|
| - const_cast<RenderText*>(this)->computePreferredLogicalWidths(0, *fallbackFonts, *glyphOverflow);
|
| - // We shouldn't change our mind once we "know".
|
| - ASSERT(!m_knownToHaveNoOverflowAndNoFallbackFonts
|
| - || (fallbackFonts->isEmpty() && glyphOverflow->isZero()));
|
| - m_knownToHaveNoOverflowAndNoFallbackFonts = fallbackFonts->isEmpty() && glyphOverflow->isZero();
|
| - }
|
| - w = m_maxWidth;
|
| - } else {
|
| - w = maxLogicalWidth();
|
| - }
|
| - } else {
|
| - w = widthFromCache(f, from, len, xPos, textDirection, fallbackFonts, glyphOverflow);
|
| - }
|
| - } else {
|
| - TextRun run = constructTextRun(const_cast<RenderText*>(this), f, this, from, len, styleRef(), textDirection);
|
| - run.setCharactersLength(textLength() - from);
|
| - ASSERT(run.charactersLength() >= run.length());
|
| -
|
| - run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun::ForceComplex);
|
| - run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
|
| - run.setXPos(xPos);
|
| - w = f.width(run, fallbackFonts, glyphOverflow);
|
| - }
|
| -
|
| - 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.
|
| - float logicalLeftSide = 0;
|
| - float logicalRightSide = 0;
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| - if (curr == firstTextBox() || curr->logicalLeft() < logicalLeftSide)
|
| - logicalLeftSide = curr->logicalLeft();
|
| - if (curr == firstTextBox() || curr->logicalRight() > logicalRightSide)
|
| - logicalRightSide = curr->logicalRight();
|
| - }
|
| -
|
| - bool isHorizontal = style()->isHorizontalWritingMode();
|
| -
|
| - float x = isHorizontal ? logicalLeftSide : firstTextBox()->x().toFloat();
|
| - float y = isHorizontal ? firstTextBox()->y().toFloat() : logicalLeftSide;
|
| - float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastTextBox()->logicalBottom() - x;
|
| - float height = isHorizontal ? lastTextBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide;
|
| - result = enclosingIntRect(FloatRect(x, y, width, height));
|
| - }
|
| -
|
| - return result;
|
| -}
|
| -
|
| -LayoutRect RenderText::linesVisualOverflowBoundingBox() const
|
| -{
|
| - if (!firstTextBox())
|
| - return LayoutRect();
|
| -
|
| - // Return the width of the minimal left side and the maximal right side.
|
| - LayoutUnit logicalLeftSide = LayoutUnit::max();
|
| - LayoutUnit logicalRightSide = LayoutUnit::min();
|
| - for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox()) {
|
| - LayoutRect logicalVisualOverflow = curr->logicalOverflowRect();
|
| - logicalLeftSide = std::min(logicalLeftSide, logicalVisualOverflow.x());
|
| - logicalRightSide = std::max(logicalRightSide, logicalVisualOverflow.maxX());
|
| - }
|
| -
|
| - LayoutUnit logicalTop = firstTextBox()->logicalTopVisualOverflow();
|
| - LayoutUnit logicalWidth = logicalRightSide - logicalLeftSide;
|
| - LayoutUnit logicalHeight = lastTextBox()->logicalBottomVisualOverflow() - logicalTop;
|
| -
|
| - LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight);
|
| - if (!style()->isHorizontalWritingMode())
|
| - rect = rect.transposedRect();
|
| - return rect;
|
| -}
|
| -
|
| -LayoutRect RenderText::clippedOverflowRectForPaintInvalidation(const LayoutBoxModelObject* paintInvalidationContainer, const PaintInvalidationState* paintInvalidationState) const
|
| -{
|
| - if (style()->visibility() != VISIBLE)
|
| - return LayoutRect();
|
| -
|
| - LayoutRect paintInvalidationRect(linesVisualOverflowBoundingBox());
|
| - mapRectToPaintInvalidationBacking(paintInvalidationContainer, paintInvalidationRect, paintInvalidationState);
|
| - return paintInvalidationRect;
|
| -}
|
| -
|
| -LayoutRect RenderText::selectionRectForPaintInvalidation(const LayoutBoxModelObject* paintInvalidationContainer) const
|
| -{
|
| - ASSERT(!needsLayout());
|
| -
|
| - if (selectionState() == SelectionNone)
|
| - return LayoutRect();
|
| - LayoutBlock* cb = containingBlock();
|
| - if (!cb)
|
| - return LayoutRect();
|
| -
|
| - // 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;
|
| - }
|
| -
|
| - LayoutRect rect;
|
| -
|
| - if (startPos == endPos)
|
| - return rect;
|
| -
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
|
| - rect.unite(box->localSelectionRect(startPos, endPos));
|
| - rect.unite(LayoutRect(ellipsisRectForBox(box, startPos, endPos)));
|
| - }
|
| -
|
| - mapRectToPaintInvalidationBacking(paintInvalidationContainer, rect, 0);
|
| - // FIXME: groupedMapping() leaks the squashing abstraction.
|
| - if (paintInvalidationContainer->layer()->groupedMapping())
|
| - Layer::mapRectToPaintBackingCoordinates(paintInvalidationContainer, rect);
|
| - return rect;
|
| -}
|
| -
|
| -int RenderText::caretMinOffset() const
|
| -{
|
| - InlineTextBox* box = firstTextBox();
|
| - if (!box)
|
| - return 0;
|
| - int minOffset = box->start();
|
| - for (box = box->nextTextBox(); box; box = box->nextTextBox())
|
| - minOffset = std::min<int>(minOffset, box->start());
|
| - return minOffset;
|
| -}
|
| -
|
| -int RenderText::caretMaxOffset() const
|
| -{
|
| - InlineTextBox* box = lastTextBox();
|
| - if (!lastTextBox())
|
| - return textLength();
|
| -
|
| - int maxOffset = box->start() + box->len();
|
| - for (box = box->prevTextBox(); box; box = box->prevTextBox())
|
| - maxOffset = std::max<int>(maxOffset, box->start() + box->len());
|
| - return maxOffset;
|
| -}
|
| -
|
| -unsigned RenderText::renderedTextLength() const
|
| -{
|
| - int l = 0;
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - l += box->len();
|
| - return l;
|
| -}
|
| -
|
| -int RenderText::previousOffset(int current) const
|
| -{
|
| - if (isAllASCII() || m_text.is8Bit())
|
| - return current - 1;
|
| -
|
| - StringImpl* textImpl = m_text.impl();
|
| - TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(), textImpl->length());
|
| - if (!iterator)
|
| - return current - 1;
|
| -
|
| - long result = iterator->preceding(current);
|
| - if (result == TextBreakDone)
|
| - result = current - 1;
|
| -
|
| -
|
| - return result;
|
| -}
|
| -
|
| -#if OS(POSIX)
|
| -
|
| -#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;
|
| -}
|
| -
|
| -inline bool isMark(UChar32 c)
|
| -{
|
| - int8_t charType = u_charType(c);
|
| - return charType == U_NON_SPACING_MARK || charType == U_ENCLOSING_MARK || charType == U_COMBINING_SPACING_MARK;
|
| -}
|
| -
|
| -inline bool isRegionalIndicator(UChar32 c)
|
| -{
|
| - // National flag emoji each consists of a pair of regional indicator symbols.
|
| - return 0x1F1E6 <= c && c <= 0x1F1FF;
|
| -}
|
| -
|
| -#endif
|
| -
|
| -int RenderText::previousOffsetForBackwardDeletion(int current) const
|
| -{
|
| -#if OS(POSIX)
|
| - ASSERT(m_text);
|
| - StringImpl& text = *m_text.impl();
|
| - UChar32 character;
|
| - bool sawRegionalIndicator = false;
|
| - while (current > 0) {
|
| - if (U16_IS_TRAIL(text[--current]))
|
| - --current;
|
| - if (current < 0)
|
| - break;
|
| -
|
| - UChar32 character = text.characterStartingAt(current);
|
| -
|
| - if (sawRegionalIndicator) {
|
| - // We don't check if the pair of regional indicator symbols before current position can actually be combined
|
| - // into a flag, and just delete it. This may not agree with how the pair is rendered in edge cases,
|
| - // but is good enough in practice.
|
| - if (isRegionalIndicator(character))
|
| - break;
|
| - // Don't delete a preceding character that isn't a regional indicator symbol.
|
| - U16_FWD_1_UNSAFE(text, current);
|
| - }
|
| -
|
| - // We don't combine characters in Armenian ... Limbu range for backward deletion.
|
| - if ((character >= 0x0530) && (character < 0x1950))
|
| - break;
|
| -
|
| - if (isRegionalIndicator(character)) {
|
| - sawRegionalIndicator = true;
|
| - continue;
|
| - }
|
| -
|
| - if (!isMark(character) && (character != 0xFF9E) && (character != 0xFF9F))
|
| - break;
|
| - }
|
| -
|
| - if (current <= 0)
|
| - return current;
|
| -
|
| - // Hangul
|
| - character = text.characterStartingAt(current);
|
| - if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))) {
|
| - HangulState state;
|
| -
|
| - 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;
|
| -
|
| - while (current > 0 && ((character = 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 Unix-like delete by one code point.
|
| - if (U16_IS_TRAIL(m_text[--current]))
|
| - --current;
|
| - if (current < 0)
|
| - current = 0;
|
| - return current;
|
| -#endif
|
| -}
|
| -
|
| -int RenderText::nextOffset(int current) const
|
| -{
|
| - if (isAllASCII() || m_text.is8Bit())
|
| - return current + 1;
|
| -
|
| - StringImpl* textImpl = m_text.impl();
|
| - TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(), textImpl->length());
|
| - if (!iterator)
|
| - return current + 1;
|
| -
|
| - long result = iterator->following(current);
|
| - if (result == TextBreakDone)
|
| - result = current + 1;
|
| -
|
| - return result;
|
| -}
|
| -
|
| -bool RenderText::computeCanUseSimpleFontCodePath() const
|
| -{
|
| - if (isAllASCII() || m_text.is8Bit())
|
| - return true;
|
| - return Character::characterRangeCodePath(characters16(), length()) == SimplePath;
|
| -}
|
| -
|
| -#if ENABLE(ASSERT)
|
| -
|
| -void RenderText::checkConsistency() const
|
| -{
|
| -#ifdef CHECK_CONSISTENCY
|
| - const InlineTextBox* prev = 0;
|
| - for (const InlineTextBox* child = m_firstTextBox; child != 0; child = child->nextTextBox()) {
|
| - ASSERT(child->renderer() == this);
|
| - ASSERT(child->prevTextBox() == prev);
|
| - prev = child;
|
| - }
|
| - ASSERT(prev == m_lastTextBox);
|
| -#endif
|
| -}
|
| -
|
| -#endif
|
| -
|
| -void RenderText::momentarilyRevealLastTypedCharacter(unsigned lastTypedCharacterOffset)
|
| -{
|
| - if (!gSecureTextTimers)
|
| - gSecureTextTimers = new SecureTextTimerMap;
|
| -
|
| - SecureTextTimer* secureTextTimer = gSecureTextTimers->get(this);
|
| - if (!secureTextTimer) {
|
| - secureTextTimer = new SecureTextTimer(this);
|
| - gSecureTextTimers->add(this, secureTextTimer);
|
| - }
|
| - secureTextTimer->restartWithNewText(lastTypedCharacterOffset);
|
| -}
|
| -
|
| -PassRefPtr<AbstractInlineTextBox> RenderText::firstAbstractInlineTextBox()
|
| -{
|
| - return AbstractInlineTextBox::getOrCreate(this, m_firstTextBox);
|
| -}
|
| -
|
| -void RenderText::invalidateDisplayItemClients(DisplayItemList* displayItemList) const
|
| -{
|
| - LayoutObject::invalidateDisplayItemClients(displayItemList);
|
| - for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
|
| - displayItemList->invalidate(box->displayItemClient());
|
| -}
|
| -
|
| -} // namespace blink
|
|
|