summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/RenderText.cpp
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
committerOswald Buddenhagen <oswald.buddenhagen@qt.io>2017-05-30 12:48:17 +0200
commit881da28418d380042aa95a97f0cbd42560a64f7c (patch)
treea794dff3274695e99c651902dde93d934ea7a5af /Source/WebCore/rendering/RenderText.cpp
parent7e104c57a70fdf551bb3d22a5d637cdcbc69dbea (diff)
parent0fcedcd17cc00d3dd44c718b3cb36c1033319671 (diff)
downloadqtwebkit-881da28418d380042aa95a97f0cbd42560a64f7c.tar.gz
Merge 'wip/next' into dev
Change-Id: Iff9ee5e23bb326c4371ec8ed81d56f2f05d680e9
Diffstat (limited to 'Source/WebCore/rendering/RenderText.cpp')
-rw-r--r--Source/WebCore/rendering/RenderText.cpp1664
1 files changed, 665 insertions, 999 deletions
diff --git a/Source/WebCore/rendering/RenderText.cpp b/Source/WebCore/rendering/RenderText.cpp
index cc7607f74..117ec4e5e 100644
--- a/Source/WebCore/rendering/RenderText.cpp
+++ b/Source/WebCore/rendering/RenderText.cpp
@@ -1,7 +1,7 @@
/*
* (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) 2004-2007, 2013-2015 Apple Inc. All rights reserved.
* Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
* Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com)
*
@@ -26,28 +26,40 @@
#include "RenderText.h"
#include "AXObjectCache.h"
+#include "BreakingContext.h"
+#include "CharacterProperties.h"
#include "EllipsisBox.h"
#include "FloatQuad.h"
-#include "FontTranscoder.h"
+#include "Frame.h"
#include "FrameView.h"
#include "Hyphenation.h"
#include "InlineTextBox.h"
#include "Range.h"
-#include "RenderArena.h"
#include "RenderBlock.h"
#include "RenderCombineText.h"
+#include "RenderInline.h"
#include "RenderLayer.h"
#include "RenderView.h"
#include "Settings.h"
+#include "SimpleLineLayoutFunctions.h"
#include "Text.h"
#include "TextBreakIterator.h"
#include "TextResourceDecoder.h"
#include "VisiblePosition.h"
#include "break_lines.h"
+#include <wtf/NeverDestroyed.h>
#include <wtf/text/StringBuffer.h>
+#include <wtf/text/StringBuilder.h>
#include <wtf/unicode/CharacterNames.h>
-using namespace std;
+#if PLATFORM(IOS)
+#include "Document.h"
+#include "EditorClient.h"
+#include "LogicalSelectionOffsetCaches.h"
+#include "Page.h"
+#include "SelectionRect.h"
+#endif
+
using namespace WTF;
using namespace Unicode;
@@ -55,6 +67,9 @@ namespace WebCore {
struct SameSizeAsRenderText : public RenderObject {
uint32_t bitfields : 16;
+#if ENABLE(IOS_TEXT_AUTOSIZING)
+ float candidateTextSize;
+#endif
float widths[4];
String text;
void* pointers[2];
@@ -62,47 +77,70 @@ struct SameSizeAsRenderText : public RenderObject {
COMPILE_ASSERT(sizeof(RenderText) == sizeof(SameSizeAsRenderText), RenderText_should_stay_small);
-class SecureTextTimer;
-typedef HashMap<RenderText*, SecureTextTimer*> SecureTextTimerMap;
-static SecureTextTimerMap* gSecureTextTimers = 0;
-
-class SecureTextTimer : public TimerBase {
+class SecureTextTimer final : private TimerBase {
+ WTF_MAKE_FAST_ALLOCATED;
public:
- SecureTextTimer(RenderText* renderText)
- : m_renderText(renderText)
- , m_lastTypedCharacterOffset(-1)
- {
- }
+ explicit SecureTextTimer(RenderText&);
+ void restart(unsigned offsetAfterLastTypedCharacter);
- void restartWithNewText(unsigned lastTypedCharacterOffset)
- {
- m_lastTypedCharacterOffset = lastTypedCharacterOffset;
- if (Settings* settings = m_renderText->document()->settings())
- startOneShot(settings->passwordEchoDurationInSeconds());
- }
- void invalidate() { m_lastTypedCharacterOffset = -1; }
- unsigned lastTypedCharacterOffset() { return m_lastTypedCharacterOffset; }
+ unsigned takeOffsetAfterLastTypedCharacter();
private:
- virtual void fired()
- {
- ASSERT(gSecureTextTimers->contains(m_renderText));
- m_renderText->setText(m_renderText->text(), true /* forcing setting text as it may be masked later */);
- }
-
- RenderText* m_renderText;
- int m_lastTypedCharacterOffset;
+ virtual void fired() override;
+ RenderText& m_renderer;
+ unsigned m_offsetAfterLastTypedCharacter { 0 };
};
-static void makeCapitalized(String* string, UChar previous)
+typedef HashMap<RenderText*, std::unique_ptr<SecureTextTimer>> SecureTextTimerMap;
+
+static SecureTextTimerMap& secureTextTimers()
+{
+ static NeverDestroyed<SecureTextTimerMap> map;
+ return map.get();
+}
+
+inline SecureTextTimer::SecureTextTimer(RenderText& renderer)
+ : m_renderer(renderer)
+{
+}
+
+inline void SecureTextTimer::restart(unsigned offsetAfterLastTypedCharacter)
+{
+ m_offsetAfterLastTypedCharacter = offsetAfterLastTypedCharacter;
+ startOneShot(m_renderer.frame().settings().passwordEchoDurationInSeconds());
+}
+
+inline unsigned SecureTextTimer::takeOffsetAfterLastTypedCharacter()
+{
+ unsigned offset = m_offsetAfterLastTypedCharacter;
+ m_offsetAfterLastTypedCharacter = 0;
+ return offset;
+}
+
+void SecureTextTimer::fired()
{
+ ASSERT(secureTextTimers().get(&m_renderer) == this);
+ m_offsetAfterLastTypedCharacter = 0;
+ m_renderer.setText(m_renderer.text(), true /* forcing setting text as it may be masked later */);
+}
+
+static HashMap<const RenderText*, String>& originalTextMap()
+{
+ static NeverDestroyed<HashMap<const RenderText*, String>> map;
+ return map;
+}
+
+void makeCapitalized(String* string, UChar previous)
+{
+ // FIXME: Need to change this to use u_strToTitle instead of u_totitle and to consider locale.
+
if (string->isNull())
return;
unsigned length = string->length();
const StringImpl& stringImpl = *string->impl();
- if (length >= numeric_limits<unsigned>::max())
+ if (length >= std::numeric_limits<unsigned>::max())
CRASH();
StringBuffer<UChar> stringWithPrevious(length + 1);
@@ -115,7 +153,7 @@ static void makeCapitalized(String* string, UChar previous)
stringWithPrevious[i] = stringImpl[i - 1];
}
- TextBreakIterator* boundary = wordBreakIterator(stringWithPrevious.characters(), length + 1);
+ TextBreakIterator* boundary = wordBreakIterator(StringView(stringWithPrevious.characters(), length + 1));
if (!boundary)
return;
@@ -126,7 +164,7 @@ static void makeCapitalized(String* string, UChar previous)
int32_t startOfWord = textBreakFirst(boundary);
for (endOfWord = textBreakNext(boundary); endOfWord != TextBreakDone; startOfWord = endOfWord, endOfWord = textBreakNext(boundary)) {
if (startOfWord) // Ignore first char of previous string
- result.append(stringImpl[startOfWord - 1] == noBreakSpace ? noBreakSpace : toTitleCase(stringWithPrevious[startOfWord]));
+ result.append(stringImpl[startOfWord - 1] == noBreakSpace ? noBreakSpace : u_totitle(stringWithPrevious[startOfWord]));
for (int i = startOfWord + 1; i < endOfWord; i++)
result.append(stringImpl[i - 1]);
}
@@ -134,63 +172,73 @@ static void makeCapitalized(String* string, UChar previous)
*string = result.toString();
}
-RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str)
- : RenderObject(!node || node->isDocumentNode() ? 0 : node)
+inline RenderText::RenderText(Node& node, const String& text)
+ : RenderObject(node)
, m_hasTab(false)
, m_linesDirty(false)
, m_containsReversedText(false)
+ , m_isAllASCII(text.containsOnlyASCII())
, m_knownToHaveNoOverflowAndNoFallbackFonts(false)
- , m_needsTranscoding(false)
+ , m_useBackslashAsYenSymbol(false)
+ , m_originalTextDiffersFromRendered(false)
+#if ENABLE(IOS_TEXT_AUTOSIZING)
+ , m_candidateComputedTextSize(0)
+#endif
, m_minWidth(-1)
, m_maxWidth(-1)
, m_beginMinWidth(0)
, m_endMinWidth(0)
- , m_text(str)
- , m_firstTextBox(0)
- , m_lastTextBox(0)
+ , m_text(text)
{
- 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();
+ ASSERT(!m_text.isNull());
setIsText();
+ m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
+ view().frameView().incrementVisuallyNonEmptyCharacterCount(textLength());
+}
- view()->frameView()->incrementVisuallyNonEmptyCharacterCount(m_text.length());
+RenderText::RenderText(Text& textNode, const String& text)
+ : RenderText(static_cast<Node&>(textNode), text)
+{
}
-#ifndef NDEBUG
+RenderText::RenderText(Document& document, const String& text)
+ : RenderText(static_cast<Node&>(document), text)
+{
+}
RenderText::~RenderText()
{
- ASSERT(!m_firstTextBox);
- ASSERT(!m_lastTextBox);
+ if (m_originalTextDiffersFromRendered)
+ originalTextMap().remove(this);
}
-#endif
-
const char* RenderText::renderName() const
{
return "RenderText";
}
-bool RenderText::isTextFragment() const
+Text* RenderText::textNode() const
{
- return false;
+ return downcast<Text>(RenderObject::node());
}
-bool RenderText::isWordBreak() const
+bool RenderText::isTextFragment() const
{
return false;
}
-void RenderText::updateNeedsTranscoding()
+bool RenderText::computeUseBackslashAsYenSymbol() const
{
- const TextEncoding* encoding = document()->decoder() ? &document()->decoder()->encoding() : 0;
- m_needsTranscoding = fontTranscoder().needsTranscoding(style()->font().fontDescription(), encoding);
+ const RenderStyle& style = this->style();
+ const auto& fontDescription = style.fontDescription();
+ if (style.fontCascade().useBackslashAsYenSymbol())
+ return true;
+ if (fontDescription.isSpecifiedFont())
+ return false;
+ const TextEncoding* encoding = document().decoder() ? &document().decoder()->encoding() : 0;
+ if (encoding && encoding->backslashAsCurrencySymbol() != '\\')
+ return true;
+ return false;
}
void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
@@ -204,147 +252,64 @@ void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyl
m_knownToHaveNoOverflowAndNoFallbackFonts = false;
}
- RenderStyle* newStyle = style();
+ const RenderStyle& newStyle = style();
bool needsResetText = false;
if (!oldStyle) {
- updateNeedsTranscoding();
- needsResetText = m_needsTranscoding;
- } else if (oldStyle->font().needsTranscoding() != newStyle->font().needsTranscoding() || (newStyle->font().needsTranscoding() && oldStyle->font().firstFamily() != newStyle->font().firstFamily())) {
- updateNeedsTranscoding();
+ m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
+ needsResetText = m_useBackslashAsYenSymbol;
+ } else if (oldStyle->fontCascade().useBackslashAsYenSymbol() != newStyle.fontCascade().useBackslashAsYenSymbol()) {
+ m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
needsResetText = true;
}
ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE;
ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE;
- if (needsResetText || oldTransform != newStyle->textTransform() || oldSecurity != newStyle->textSecurity())
- transformText();
+ if (needsResetText || oldTransform != newStyle.textTransform() || oldSecurity != newStyle.textSecurity())
+ RenderText::setText(originalText(), true);
}
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();
+ if (!documentBeingDestroyed())
+ m_lineBoxes.removeAllFromParent(*this);
+#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
+ else
+ m_lineBoxes.invalidateParentChildLists();
+#endif
+ m_lineBoxes.deleteAll();
}
void RenderText::willBeDestroyed()
{
- if (SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->take(this) : 0)
- delete secureTextTimer;
+ secureTextTimers().remove(this);
removeAndDestroyTextBoxes();
RenderObject::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()
+void RenderText::deleteLineBoxesBeforeSimpleLineLayout()
{
- 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;
- }
+ m_lineBoxes.deleteAll();
}
-PassRefPtr<StringImpl> RenderText::originalText() const
+String RenderText::originalText() const
{
- Node* e = node();
- return (e && e->isTextNode()) ? toText(e)->dataImpl() : 0;
+ return m_originalTextDiffersFromRendered ? originalTextMap().get(this) : m_text;
}
void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
{
- for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
- rects.append(enclosingIntRect(FloatRect(accumulatedOffset + box->topLeft(), box->size())));
-}
-
-static FloatRect localQuadForTextBox(InlineTextBox* box, unsigned start, unsigned end, bool useSelectionHeight)
-{
- unsigned realEnd = 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);
+ if (auto* layout = simpleLineLayout()) {
+ rects.appendVector(SimpleLineLayout::collectAbsoluteRects(*this, *layout, accumulatedOffset));
+ return;
}
- return FloatRect();
+ rects.appendVector(m_lineBoxes.absoluteRects(accumulatedOffset));
}
-void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed)
+Vector<IntRect> RenderText::absoluteRectsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
{
+ const_cast<RenderText&>(*this).ensureLineBoxes();
+
// 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
@@ -352,300 +317,129 @@ void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, u
// 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) {
- FloatRect r = box->calculateBoundaries();
- if (useSelectionHeight) {
- LayoutRect selectionRect = box->localSelectionRect(start, end);
- if (box->isHorizontal()) {
- r.setHeight(selectionRect.height());
- r.setY(selectionRect.y());
- } else {
- r.setWidth(selectionRect.width());
- r.setX(selectionRect.x());
- }
- }
- 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();
+ start = std::min(start, static_cast<unsigned>(INT_MAX));
+ end = std::min(end, static_cast<unsigned>(INT_MAX));
- unsigned short truncation = box->truncation();
- if (truncation == cNoTruncation)
- return IntRect();
-
- IntRect rect;
- if (EllipsisBox* ellipsis = box->root()->ellipsisBox()) {
- int ellipsisStartPosition = max<int>(startPos - box->start(), 0);
- int ellipsisEndPosition = 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();
-
- // 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);
+ return m_lineBoxes.absoluteRectsForRange(*this, start, end, useSelectionHeight, wasFixed);
}
-void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed)
+#if PLATFORM(IOS)
+// This function is similar in spirit to addLineBoxRects, but returns rectangles
+// which are annotated with additional state which helps the iPhone draw selections in its unique way.
+// Full annotations are added in this class.
+void RenderText::collectSelectionRects(Vector<SelectionRect>& rects, unsigned start, unsigned end)
{
- // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
+ // FIXME: 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));
-
+ ASSERT(end == std::numeric_limits<unsigned>::max() || end <= std::numeric_limits<int>::max());
+ ASSERT(start <= std::numeric_limits<int>::max());
+ start = std::min(start, static_cast<unsigned>(std::numeric_limits<int>::max()));
+ end = std::min(end, static_cast<unsigned>(std::numeric_limits<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();
- if (useSelectionHeight) {
- LayoutRect selectionRect = box->localSelectionRect(start, end);
- if (box->isHorizontal()) {
- r.setHeight(selectionRect.height());
- r.setY(selectionRect.y());
- } else {
- r.setWidth(selectionRect.width());
- r.setX(selectionRect.x());
- }
- }
- quads.append(localToAbsoluteQuad(r, 0, wasFixed));
- } else {
- FloatRect rect = localQuadForTextBox(box, start, end, useSelectionHeight);
- if (!rect.isZero())
- quads.append(localToAbsoluteQuad(rect, 0, wasFixed));
+ LayoutRect rect;
+ // Note, box->end() returns the index of the last character, not the index past it.
+ if (start <= box->start() && box->end() < end)
+ rect = box->localSelectionRect(start, end);
+ else {
+ unsigned realEnd = std::min(box->end() + 1, end);
+ rect = box->localSelectionRect(start, realEnd);
+ if (rect.isEmpty())
+ continue;
}
- }
-}
-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 (box->root().isFirstAfterPageBreak()) {
+ if (box->isHorizontal())
+ rect.shiftYEdgeTo(box->root().lineTopWithLeading());
+ else
+ rect.shiftXEdgeTo(box->root().lineTopWithLeading());
+ }
- if (!m_firstTextBox)
- return 0;
+ RenderBlock* containingBlock = this->containingBlock();
+ // Map rect, extended left to leftOffset, and right to rightOffset, through transforms to get minX and maxX.
+ LogicalSelectionOffsetCaches cache(*containingBlock);
+ LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, box->logicalTop(), cache);
+ LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, box->logicalTop(), cache);
+ LayoutRect extentsRect = rect;
+ if (box->isHorizontal()) {
+ extentsRect.setX(leftOffset);
+ extentsRect.setWidth(rightOffset - leftOffset);
+ } else {
+ extentsRect.setY(leftOffset);
+ extentsRect.setHeight(rightOffset - leftOffset);
+ }
+ extentsRect = localToAbsoluteQuad(FloatRect(extentsRect)).enclosingBoundingBox();
+ if (!box->isHorizontal())
+ extentsRect = extentsRect.transposedRect();
+ bool isFirstOnLine = !box->previousOnLineExists();
+ bool isLastOnLine = !box->nextOnLineExists();
+ if (containingBlock->isRubyBase() || containingBlock->isRubyText())
+ isLastOnLine = !containingBlock->containingBlock()->inlineBoxWrapper()->nextOnLineExists();
+
+ bool containsStart = box->start() <= start && box->end() + 1 >= start;
+ bool containsEnd = box->start() <= end && box->end() + 1 >= end;
+
+ bool isFixed = false;
+ IntRect absRect = localToAbsoluteQuad(FloatRect(rect), UseTransforms, &isFixed).enclosingBoundingBox();
+ bool boxIsHorizontal = !box->isSVGInlineTextBox() ? box->isHorizontal() : !style().svgStyle().isVerticalWritingMode();
+ // If the containing block is an inline element, we want to check the inlineBoxWrapper orientation
+ // to determine the orientation of the block. In this case we also use the inlineBoxWrapper to
+ // determine if the element is the last on the line.
+ if (containingBlock->inlineBoxWrapper()) {
+ if (containingBlock->inlineBoxWrapper()->isHorizontal() != boxIsHorizontal) {
+ boxIsHorizontal = containingBlock->inlineBoxWrapper()->isHorizontal();
+ isLastOnLine = !containingBlock->inlineBoxWrapper()->nextOnLineExists();
+ }
+ }
- InlineTextBox* s = m_firstTextBox;
- int off = s->len();
- while (offset > off && s->nextTextBox()) {
- s = s->nextTextBox();
- off = s->start() + s->len();
+ rects.append(SelectionRect(absRect, box->direction(), extentsRect.x(), extentsRect.maxX(), extentsRect.maxY(), 0, box->isLineBreak(), isFirstOnLine, isLastOnLine, containsStart, containsEnd, boxIsHorizontal, isFixed, containingBlock->isRubyText(), view().pageNumberForBlockProgressionOffset(absRect.x())));
}
- // we are now in the correct text run
- pos = (offset > off ? s->len() : s->len() - (off - offset) );
- return s;
}
+#endif
-enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart };
-
-static bool lineDirectionPointFitsInBox(int pointLineDirection, InlineTextBox* box, ShouldAffinityBeDownstream& shouldAffinityBeDownstream)
+Vector<FloatQuad> RenderText::absoluteQuadsClippedToEllipsis() const
{
- 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;
+ if (auto* layout = simpleLineLayout()) {
+ ASSERT(style().textOverflow() != TextOverflowEllipsis);
+ return SimpleLineLayout::collectAbsoluteQuads(*this, *layout, nullptr);
}
-
- // 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;
+ return m_lineBoxes.absoluteQuads(*this, nullptr, RenderTextLineBoxes::ClipToEllipsis);
}
-static VisiblePosition createVisiblePositionForBox(const InlineBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
+void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
{
- 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;
+ if (auto* layout = simpleLineLayout()) {
+ quads.appendVector(SimpleLineLayout::collectAbsoluteQuads(*this, *layout, wasFixed));
+ return;
}
- return box->renderer()->createVisiblePosition(offset, affinity);
+ quads.appendVector(m_lineBoxes.absoluteQuads(*this, wasFixed, RenderTextLineBoxes::NoClipping));
}
-static VisiblePosition createVisiblePositionAfterAdjustingOffsetForBiDi(const InlineTextBox* box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
+Vector<FloatQuad> RenderText::absoluteQuadsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
{
- ASSERT(box);
- ASSERT(box->renderer());
- ASSERT(offset >= 0);
-
- if (offset && static_cast<unsigned>(offset) < box->len())
- return createVisiblePositionForBox(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 createVisiblePositionForBox(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 createVisiblePositionForBox(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 createVisiblePositionForBox(rightmostBox,
- box->isLeftToRightDirection() ? rightmostBox->caretMaxOffset() : rightmostBox->caretMinOffset(), shouldAffinityBeDownstream);
- }
+ const_cast<RenderText&>(*this).ensureLineBoxes();
- return createVisiblePositionForBox(box, box->caretRightmostOffset(), shouldAffinityBeDownstream);
- }
-
- const InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak();
- if ((nextBox && nextBox->bidiLevel() == box->bidiLevel())
- || box->renderer()->containingBlock()->style()->direction() == box->direction())
- return createVisiblePositionForBox(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 createVisiblePositionForBox(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 createVisiblePositionForBox(leftmostBox,
- box->isLeftToRightDirection() ? leftmostBox->caretMinOffset() : leftmostBox->caretMaxOffset(), shouldAffinityBeDownstream);
- }
-
- return createVisiblePositionForBox(box, box->caretLeftmostOffset(), shouldAffinityBeDownstream);
+ // 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));
+
+ return m_lineBoxes.absoluteQuadsForRange(*this, start, end, useSelectionHeight, wasFixed);
}
-VisiblePosition RenderText::positionForPoint(const LayoutPoint& point)
+VisiblePosition RenderText::positionForPoint(const LayoutPoint& point, const RenderRegion*)
{
- if (!firstTextBox() || textLength() == 0)
- return createVisiblePosition(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 = min(rootBox->selectionTop(), rootBox->lineTop());
- if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirection == top)) {
- LayoutUnit bottom = rootBox->selectionBottom();
- if (rootBox->nextRootBox())
- bottom = min(bottom, rootBox->nextRootBox()->lineTop());
-
- if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockDirection == bottom)) {
- ShouldAffinityBeDownstream shouldAffinityBeDownstream;
- if (lineDirectionPointFitsInBox(pointLineDirection, box, shouldAffinityBeDownstream))
- return createVisiblePositionAfterAdjustingOffsetForBiDi(box, box->offsetForPosition(pointLineDirection), shouldAffinityBeDownstream);
- }
- }
- lastBox = box;
- }
+ ensureLineBoxes();
- if (lastBox) {
- ShouldAffinityBeDownstream shouldAffinityBeDownstream;
- lineDirectionPointFitsInBox(pointLineDirection, lastBox, shouldAffinityBeDownstream);
- return createVisiblePositionAfterAdjustingOffsetForBiDi(lastBox, lastBox->offsetForPosition(pointLineDirection) + lastBox->start(), shouldAffinityBeDownstream);
- }
- return createVisiblePosition(0, DOWNSTREAM);
+ return m_lineBoxes.positionForPoint(*this, point);
}
LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, LayoutUnit* extraWidthToEndOfLine)
@@ -653,81 +447,20 @@ LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, Lay
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);
-
- RenderBlock* cb = containingBlock();
- RenderStyle* cbStyle = cb->style();
-
- float leftEdge;
- float rightEdge;
- leftEdge = min<float>(0, rootLeft);
- rightEdge = max<float>(cb->logicalWidth(), 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;
- }
-
- if (rightAligned) {
- left = max(left, leftEdge);
- left = min(left, rootRight - caretWidth);
- } else {
- left = min(left, rightEdge - caretWidthRightOfOffset);
- left = max(left, rootLeft);
- }
-
- return style()->isHorizontalWritingMode() ? IntRect(left, top, caretWidth, height) : IntRect(top, left, height, caretWidth);
+ auto& box = downcast<InlineTextBox>(*inlineBox);
+ float left = box.positionForOffset(caretOffset);
+ return box.root().computeCaretRect(left, caretWidth, extraWidthToEndOfLine);
}
-ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len, float xPos, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
+ALWAYS_INLINE float RenderText::widthFromCache(const FontCascade& f, int start, int len, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow, const RenderStyle& style) const
{
- if (style()->hasTextCombine() && isCombineText()) {
- const RenderCombineText* combineText = toRenderCombineText(this);
- if (combineText->isCombined())
- return combineText->combinedTextWidth(f);
+ if (style.hasTextCombine() && is<RenderCombineText>(*this)) {
+ const RenderCombineText& combineText = downcast<RenderCombineText>(*this);
+ if (combineText.isCombined())
+ return combineText.combinedTextWidth(f);
}
- if (f.isFixedPitch() && !f.isSmallCaps() && m_isAllASCII && (!glyphOverflow || !glyphOverflow->computeBounds)) {
+ if (f.isFixedPitch() && f.fontDescription().variantSettings().isAllNormal() && m_isAllASCII && (!glyphOverflow || !glyphOverflow->computeBounds)) {
float monospaceCharacterWidth = f.spaceWidth();
float w = 0;
bool isSpace;
@@ -740,11 +473,11 @@ ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len
w += monospaceCharacterWidth;
isSpace = true;
} else if (c == '\t') {
- if (style()->collapseWhiteSpace()) {
+ if (style.collapseWhiteSpace()) {
w += monospaceCharacterWidth;
isSpace = true;
} else {
- w += f.tabWidth(style()->tabSize(), xPos + w);
+ w += f.tabWidth(style.tabSize(), xPos + w);
isSpace = false;
}
} else
@@ -759,12 +492,12 @@ ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len
return w;
}
- TextRun run = RenderBlock::constructTextRun(const_cast<RenderText*>(this), f, this, start, len, style());
+ TextRun run = RenderBlock::constructTextRun(const_cast<RenderText*>(this), f, this, start, len, style);
run.setCharactersLength(textLength() - start);
ASSERT(run.charactersLength() >= run.length());
run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
- run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
+ run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
run.setXPos(xPos);
return f.width(run, fallbackFonts, glyphOverflow);
}
@@ -776,7 +509,8 @@ void RenderText::trimmedPrefWidths(float leadWidth,
float& beginMaxW, float& endMaxW,
float& minW, float& maxW, bool& stripFrontSpaces)
{
- bool collapseWhiteSpace = style()->collapseWhiteSpace();
+ const RenderStyle& style = this->style();
+ bool collapseWhiteSpace = style.collapseWhiteSpace();
if (!collapseWhiteSpace)
stripFrontSpaces = false;
@@ -810,11 +544,11 @@ void RenderText::trimmedPrefWidths(float leadWidth,
ASSERT(m_text);
StringImpl& text = *m_text.impl();
- if (text[0] == ' ' || (text[0] == '\n' && !style()->preserveNewline()) || text[0] == '\t') {
- const Font& font = style()->font(); // FIXME: This ignores first-line.
+ if (text[0] == ' ' || (text[0] == '\n' && !style.preserveNewline()) || text[0] == '\t') {
+ const FontCascade& font = style.fontCascade(); // FIXME: This ignores first-line.
if (stripFrontSpaces) {
const UChar space = ' ';
- float spaceWidth = font.width(RenderBlock::constructTextRun(this, font, &space, 1, style()));
+ float spaceWidth = font.width(RenderBlock::constructTextRun(this, font, &space, 1, style));
maxW -= spaceWidth;
} else
maxW += font.wordSpacing();
@@ -822,12 +556,12 @@ void RenderText::trimmedPrefWidths(float leadWidth,
stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
- if (!style()->autoWrap() || minW > maxW)
+ 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.
+ const FontCascade& f = style.fontCascade(); // FIXME: This ignores first-line.
bool firstLine = true;
beginMaxW = maxW;
endMaxW = maxW;
@@ -837,7 +571,7 @@ void RenderText::trimmedPrefWidths(float leadWidth,
linelen++;
if (linelen) {
- endMaxW = widthFromCache(f, i, linelen, leadWidth + endMaxW, 0, 0);
+ endMaxW = widthFromCache(f, i, linelen, leadWidth + endMaxW, 0, 0, style);
if (firstLine) {
firstLine = false;
leadWidth = 0;
@@ -858,9 +592,9 @@ void RenderText::trimmedPrefWidths(float leadWidth,
}
}
-static inline bool isSpaceAccordingToStyle(UChar c, RenderStyle* style)
+static inline bool isSpaceAccordingToStyle(UChar c, const RenderStyle& style)
{
- return c == ' ' || (c == noBreakSpace && style->nbspMode() == SPACE);
+ return c == ' ' || (c == noBreakSpace && style.nbspMode() == SPACE);
}
float RenderText::minLogicalWidth() const
@@ -879,30 +613,46 @@ float RenderText::maxLogicalWidth() const
return m_maxWidth;
}
+LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak lineBreak)
+{
+ switch (lineBreak) {
+ case LineBreakAuto:
+ case LineBreakAfterWhiteSpace:
+ return LineBreakIteratorModeUAX14;
+ case LineBreakLoose:
+ return LineBreakIteratorModeUAX14Loose;
+ case LineBreakNormal:
+ return LineBreakIteratorModeUAX14Normal;
+ case LineBreakStrict:
+ return LineBreakIteratorModeUAX14Strict;
+ }
+ return LineBreakIteratorModeUAX14;
+}
+
void RenderText::computePreferredLogicalWidths(float leadWidth)
{
- HashSet<const SimpleFontData*> fallbackFonts;
+ HashSet<const Font*> fallbackFonts;
GlyphOverflow glyphOverflow;
computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow);
if (fallbackFonts.isEmpty() && !glyphOverflow.left && !glyphOverflow.right && !glyphOverflow.top && !glyphOverflow.bottom)
m_knownToHaveNoOverflowAndNoFallbackFonts = true;
}
-static inline float hyphenWidth(RenderText* renderer, const Font& font)
+static inline float hyphenWidth(RenderText* renderer, const FontCascade& font)
{
- RenderStyle* style = renderer->style();
- return font.width(RenderBlock::constructTextRun(renderer, font, style->hyphenString().string(), style));
+ const RenderStyle& style = renderer->style();
+ return font.width(RenderBlock::constructTextRun(renderer, font, style.hyphenString().string(), style));
}
-static float maxWordFragmentWidth(RenderText* renderer, RenderStyle* style, const Font& font, const UChar* word, int wordLength, int minimumPrefixLength, int minimumSuffixLength, int& suffixStart, HashSet<const SimpleFontData*>& fallbackFonts, GlyphOverflow& glyphOverflow)
+static float maxWordFragmentWidth(RenderText* renderer, const RenderStyle& style, const FontCascade& font, StringView word, int minimumPrefixLength, unsigned minimumSuffixLength, int& suffixStart, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
{
suffixStart = 0;
- if (wordLength <= minimumSuffixLength)
+ if (word.length() <= minimumSuffixLength)
return 0;
Vector<int, 8> hyphenLocations;
- int hyphenLocation = wordLength - minimumSuffixLength;
- while ((hyphenLocation = lastHyphenLocation(word, wordLength, hyphenLocation, style->locale())) >= minimumPrefixLength)
+ int hyphenLocation = word.length() - minimumSuffixLength;
+ while ((hyphenLocation = lastHyphenLocation(word, hyphenLocation, style.locale())) >= minimumPrefixLength)
hyphenLocations.append(hyphenLocation);
if (hyphenLocations.isEmpty())
@@ -915,10 +665,10 @@ static float maxWordFragmentWidth(RenderText* renderer, RenderStyle* style, cons
for (size_t k = 0; k < hyphenLocations.size(); ++k) {
int fragmentLength = hyphenLocations[k] - suffixStart;
StringBuilder fragmentWithHyphen;
- fragmentWithHyphen.append(word + suffixStart, fragmentLength);
- fragmentWithHyphen.append(style->hyphenString());
+ fragmentWithHyphen.append(word.substring(suffixStart, fragmentLength));
+ fragmentWithHyphen.append(style.hyphenString());
- TextRun run = RenderBlock::constructTextRun(renderer, font, fragmentWithHyphen.characters(), fragmentWithHyphen.length(), style);
+ TextRun run = RenderBlock::constructTextRun(renderer, font, fragmentWithHyphen.toString(), style);
run.setCharactersLength(fragmentWithHyphen.length());
run.setCharacterScanForCodePath(!renderer->canUseSimpleFontCodePath());
float fragmentWidth = font.width(run, &fallbackFonts, &glyphOverflow);
@@ -928,13 +678,13 @@ static float maxWordFragmentWidth(RenderText* renderer, RenderStyle* style, cons
continue;
suffixStart += fragmentLength;
- maxFragmentWidth = max(maxFragmentWidth, fragmentWidth);
+ maxFragmentWidth = std::max(maxFragmentWidth, fragmentWidth);
}
return maxFragmentWidth;
}
-void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const SimpleFontData*>& fallbackFonts, GlyphOverflow& glyphOverflow)
+void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
{
ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts);
@@ -943,10 +693,6 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_endMinWidth = 0;
m_maxWidth = 0;
- if (isBR())
- return;
-
- float currMinWidth = 0;
float currMaxWidth = 0;
m_hasBreakableChar = false;
m_hasBreak = false;
@@ -954,11 +700,11 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_hasBeginWS = false;
m_hasEndWS = false;
- RenderStyle* styleToUse = style();
- const Font& f = styleToUse->font(); // FIXME: This ignores first-line.
- float wordSpacing = styleToUse->wordSpacing();
+ const RenderStyle& style = this->style();
+ const FontCascade& font = style.fontCascade(); // FIXME: This ignores first-line.
+ float wordSpacing = font.wordSpacing();
int len = textLength();
- LazyLineBreakIterator breakIterator(m_text, styleToUse->locale());
+ LazyLineBreakIterator breakIterator(m_text, style.locale(), mapLineBreakToIteratorMode(style.lineBreak()));
bool needsWordSpacing = false;
bool ignoringSpaces = false;
bool isSpace = false;
@@ -969,46 +715,51 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
// Non-zero only when kerning is enabled, in which case we measure words with their trailing
// space, then subtract its width.
- float wordTrailingSpaceWidth = f.typesettingFeatures() & Kerning ? f.width(RenderBlock::constructTextRun(this, f, &space, 1, styleToUse), &fallbackFonts) + wordSpacing : 0;
-
+ WordTrailingSpace wordTrailingSpace(*this, style);
// If automatic hyphenation is allowed, we keep track of the width of the widest word (or word
// fragment) encountered so far, and only try hyphenating words that are wider.
- float maxWordWidth = numeric_limits<float>::max();
+ float maxWordWidth = std::numeric_limits<float>::max();
int minimumPrefixLength = 0;
int minimumSuffixLength = 0;
- if (styleToUse->hyphens() == HyphensAuto && canHyphenate(styleToUse->locale())) {
+ if (style.hyphens() == HyphensAuto && canHyphenate(style.locale())) {
maxWordWidth = 0;
// Map 'hyphenate-limit-{before,after}: auto;' to 2.
- minimumPrefixLength = styleToUse->hyphenationLimitBefore();
+ minimumPrefixLength = style.hyphenationLimitBefore();
if (minimumPrefixLength < 0)
minimumPrefixLength = 2;
- minimumSuffixLength = styleToUse->hyphenationLimitAfter();
+ minimumSuffixLength = style.hyphenationLimitAfter();
if (minimumSuffixLength < 0)
minimumSuffixLength = 2;
}
- int firstGlyphLeftOverflow = -1;
+ Optional<int> firstGlyphLeftOverflow;
- bool breakNBSP = styleToUse->autoWrap() && styleToUse->nbspMode() == SPACE;
- bool breakAll = (styleToUse->wordBreak() == BreakAllWordBreak || styleToUse->wordBreak() == BreakWordBreak) && styleToUse->autoWrap();
+ bool breakNBSP = style.autoWrap() && style.nbspMode() == SPACE;
+
+ // Note the deliberate omission of word-wrap and overflow-wrap from this breakAll check. Those
+ // do not affect minimum preferred sizes. Note that break-word is a non-standard value for
+ // word-break, but we support it as though it means break-all.
+ bool breakAll = (style.wordBreak() == BreakAllWordBreak || style.wordBreak() == BreakWordBreak) && style.autoWrap();
+ bool keepAllWords = style.wordBreak() == KeepAllWordBreak;
+ bool isLooseCJKMode = breakIterator.isLooseCJKMode();
for (int i = 0; i < len; i++) {
- UChar c = characterAt(i);
+ UChar c = uncheckedCharacterAt(i);
bool previousCharacterIsSpace = isSpace;
bool isNewline = false;
if (c == '\n') {
- if (styleToUse->preserveNewline()) {
+ if (style.preserveNewline()) {
m_hasBreak = true;
isNewline = true;
isSpace = false;
} else
isSpace = true;
} else if (c == '\t') {
- if (!styleToUse->collapseWhiteSpace()) {
+ if (!style.collapseWhiteSpace()) {
m_hasTab = true;
isSpace = false;
} else
@@ -1021,34 +772,31 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
if ((isSpace || isNewline) && i == len - 1)
m_hasEndWS = true;
- if (!ignoringSpaces && styleToUse->collapseWhiteSpace() && previousCharacterIsSpace && isSpace)
- ignoringSpaces = true;
-
- if (ignoringSpaces && !isSpace)
- ignoringSpaces = false;
+ ignoringSpaces |= style.collapseWhiteSpace() && previousCharacterIsSpace && isSpace;
+ ignoringSpaces &= isSpace;
// Ignore spaces and soft hyphens
if (ignoringSpaces) {
ASSERT(lastWordBoundary == i);
lastWordBoundary++;
continue;
- } else if (c == softHyphen && styleToUse->hyphens() != HyphensNone) {
- currMaxWidth += widthFromCache(f, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow);
- if (firstGlyphLeftOverflow < 0)
+ } else if (c == softHyphen && style.hyphens() != HyphensNone) {
+ currMaxWidth += widthFromCache(font, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
+ if (!firstGlyphLeftOverflow)
firstGlyphLeftOverflow = glyphOverflow.left;
lastWordBoundary = i + 1;
continue;
}
- bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP);
+ bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP, isLooseCJKMode, keepAllWords);
bool betweenWords = true;
int j = i;
- while (c != '\n' && !isSpaceAccordingToStyle(c, styleToUse) && c != '\t' && (c != softHyphen || styleToUse->hyphens() == HyphensNone)) {
+ while (c != '\n' && !isSpaceAccordingToStyle(c, style) && c != '\t' && (c != softHyphen || style.hyphens() == HyphensNone)) {
j++;
if (j == len)
break;
- c = characterAt(j);
- if (isBreakable(breakIterator, j, nextBreakable, breakNBSP) && characterAt(j - 1) != softHyphen)
+ c = uncheckedCharacterAt(j);
+ if (isBreakable(breakIterator, j, nextBreakable, breakNBSP, isLooseCJKMode, keepAllWords) && characterAt(j - 1) != softHyphen)
break;
if (breakAll) {
betweenWords = false;
@@ -1058,53 +806,60 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
int wordLen = j - i;
if (wordLen) {
- bool isSpace = (j < len) && isSpaceAccordingToStyle(c, styleToUse);
+ float currMinWidth = 0;
+ bool isSpace = (j < len) && isSpaceAccordingToStyle(c, style);
float w;
- if (wordTrailingSpaceWidth && isSpace)
- w = widthFromCache(f, i, wordLen + 1, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow) - wordTrailingSpaceWidth;
+ Optional<float> wordTrailingSpaceWidth;
+ if (isSpace)
+ wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
+ if (wordTrailingSpaceWidth)
+ w = widthFromCache(font, i, wordLen + 1, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style) - wordTrailingSpaceWidth.value();
else {
- w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow);
- if (c == softHyphen && styleToUse->hyphens() != HyphensNone)
- currMinWidth += hyphenWidth(this, f);
+ w = widthFromCache(font, i, wordLen, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
+ if (c == softHyphen && style.hyphens() != HyphensNone)
+ currMinWidth = hyphenWidth(this, font);
}
if (w > maxWordWidth) {
int suffixStart;
- float maxFragmentWidth = maxWordFragmentWidth(this, styleToUse, f, characters() + i, wordLen, minimumPrefixLength, minimumSuffixLength, suffixStart, fallbackFonts, glyphOverflow);
+ float maxFragmentWidth = maxWordFragmentWidth(this, style, font, StringView(m_text).substring(i, wordLen), minimumPrefixLength, minimumSuffixLength, suffixStart, fallbackFonts, glyphOverflow);
if (suffixStart) {
float suffixWidth;
- if (wordTrailingSpaceWidth && isSpace)
- suffixWidth = widthFromCache(f, i + suffixStart, wordLen - suffixStart + 1, leadWidth + currMaxWidth, 0, 0) - wordTrailingSpaceWidth;
+ Optional<float> wordTrailingSpaceWidth;
+ if (isSpace)
+ wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
+ if (wordTrailingSpaceWidth)
+ suffixWidth = widthFromCache(font, i + suffixStart, wordLen - suffixStart + 1, leadWidth + currMaxWidth, 0, 0, style) - wordTrailingSpaceWidth.value();
else
- suffixWidth = widthFromCache(f, i + suffixStart, wordLen - suffixStart, leadWidth + currMaxWidth, 0, 0);
+ suffixWidth = widthFromCache(font, i + suffixStart, wordLen - suffixStart, leadWidth + currMaxWidth, 0, 0, style);
- maxFragmentWidth = max(maxFragmentWidth, suffixWidth);
+ maxFragmentWidth = std::max(maxFragmentWidth, suffixWidth);
currMinWidth += maxFragmentWidth - w;
- maxWordWidth = max(maxWordWidth, maxFragmentWidth);
+ maxWordWidth = std::max(maxWordWidth, maxFragmentWidth);
} else
maxWordWidth = w;
}
- if (firstGlyphLeftOverflow < 0)
+ if (!firstGlyphLeftOverflow)
firstGlyphLeftOverflow = glyphOverflow.left;
currMinWidth += w;
if (betweenWords) {
if (lastWordBoundary == i)
currMaxWidth += w;
else
- currMaxWidth += widthFromCache(f, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow);
+ currMaxWidth += widthFromCache(font, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
lastWordBoundary = j;
}
- bool isCollapsibleWhiteSpace = (j < len) && styleToUse->isCollapsibleWhiteSpace(c);
- if (j < len && styleToUse->autoWrap())
+ 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))
+ if ((isSpace || isCollapsibleWhiteSpace) && !containsOnlyWhitespace(j, len-j))
currMaxWidth += wordSpacing;
if (firstWord) {
@@ -1118,26 +873,20 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
}
m_endMinWidth = currMinWidth;
- if (currMinWidth > m_minWidth)
- m_minWidth = currMinWidth;
- currMinWidth = 0;
+ m_minWidth = std::max(currMinWidth, m_minWidth);
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)
+ 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())
+ if (!style.autoWrap())
m_beginMinWidth = currMaxWidth;
}
@@ -1145,13 +894,13 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_maxWidth = currMaxWidth;
currMaxWidth = 0;
} else {
- TextRun run = RenderBlock::constructTextRun(this, f, this, i, 1, styleToUse);
+ TextRun run = RenderBlock::constructTextRun(this, font, this, i, 1, style);
run.setCharactersLength(len - i);
ASSERT(run.charactersLength() >= run.length());
- run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
+ run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
run.setXPos(leadWidth + currMaxWidth);
- currMaxWidth += f.width(run, &fallbackFonts);
+ currMaxWidth += font.width(run, &fallbackFonts);
glyphOverflow.right = 0;
needsWordSpacing = isSpace && !previousCharacterIsSpace && i == len - 1;
}
@@ -1160,19 +909,17 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
}
}
- if (firstGlyphLeftOverflow > 0)
- glyphOverflow.left = firstGlyphLeftOverflow;
+ glyphOverflow.left = firstGlyphLeftOverflow.valueOr(glyphOverflow.left);
if ((needsWordSpacing && len > 1) || (ignoringSpaces && !firstWord))
currMaxWidth += wordSpacing;
- m_minWidth = max(currMinWidth, m_minWidth);
- m_maxWidth = max(currMaxWidth, m_maxWidth);
+ m_maxWidth = std::max(currMaxWidth, m_maxWidth);
- if (!styleToUse->autoWrap())
+ if (!style.autoWrap())
m_minWidth = m_maxWidth;
- if (styleToUse->whiteSpace() == PRE) {
+ if (style.whiteSpace() == PRE) {
if (firstLine)
m_beginMinWidth = m_maxWidth;
m_endMinWidth = currMaxWidth;
@@ -1183,16 +930,17 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
bool RenderText::isAllCollapsibleWhitespace() const
{
+ const RenderStyle& style = this->style();
unsigned length = textLength();
if (is8Bit()) {
for (unsigned i = 0; i < length; ++i) {
- if (!style()->isCollapsibleWhiteSpace(characters8()[i]))
+ if (!style.isCollapsibleWhiteSpace(characters8()[i]))
return false;
}
return true;
}
for (unsigned i = 0; i < length; ++i) {
- if (!style()->isCollapsibleWhiteSpace(characters16()[i]))
+ if (!style.isCollapsibleWhiteSpace(characters16()[i]))
return false;
}
return true;
@@ -1209,53 +957,23 @@ bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
return currPos >= (from + len);
}
-FloatPoint RenderText::firstRunOrigin() const
+IntPoint RenderText::firstRunLocation() const
{
- return IntPoint(firstRunX(), firstRunY());
-}
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::computeFirstRunLocation(*this, *layout);
-float RenderText::firstRunX() const
-{
- return m_firstTextBox ? m_firstTextBox->x() : 0;
+ return m_lineBoxes.firstRunLocation();
}
-float RenderText::firstRunY() const
-{
- return m_firstTextBox ? m_firstTextBox->y() : 0;
-}
-
void RenderText::setSelectionState(SelectionState state)
{
+ if (state != SelectionNone)
+ ensureLineBoxes();
+
RenderObject::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)) {
- RootInlineBox* root = box->root();
- if (root)
- root->setHasSelectedChildren(true);
- }
- }
- } else {
- for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
- RootInlineBox* root = box->root();
- if (root)
- root->setHasSelectedChildren(state == SelectionInside);
- }
- }
- }
+ if (canUpdateSelectionOnRootLineBoxes())
+ m_lineBoxes.setSelectionState(*this, state);
// The containing block can be null in case of an orphaned tree.
RenderBlock* containingBlock = this->containingBlock();
@@ -1263,99 +981,26 @@ void RenderText::setSelectionState(SelectionState state)
containingBlock->setSelectionState(state);
}
-void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset, unsigned len, bool force)
+void RenderText::setTextWithOffset(const String& text, unsigned offset, unsigned len, bool force)
{
- if (!force && equal(m_text.impl(), text.get()))
+ if (!force && m_text == text)
return;
- unsigned oldLen = textLength();
- unsigned newLen = text->length();
- int delta = newLen - oldLen;
+ int delta = text.length() - textLength();
unsigned end = len ? offset + len - 1 : offset;
- RootInlineBox* firstRootBox = 0;
- RootInlineBox* lastRootBox = 0;
-
- bool dirtiedLines = false;
+ m_linesDirty = simpleLineLayout() || m_lineBoxes.dirtyRange(*this, offset, end, delta);
- // 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;
- 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;
- } 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(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);
+ setText(text, force || m_linesDirty);
}
-void RenderText::transformText()
+static inline bool isInlineFlowOrEmptyText(const RenderObject& renderer)
{
- if (RefPtr<StringImpl> textToTransform = originalText())
- setText(textToTransform.release(), true);
-}
-
-static inline bool isInlineFlowOrEmptyText(const RenderObject* o)
-{
- if (o->isRenderInline())
+ if (is<RenderInline>(renderer))
return true;
- if (!o->isText())
+ if (!is<RenderText>(renderer))
return false;
- StringImpl* text = toRenderText(o)->text();
+ StringImpl* text = downcast<RenderText>(renderer).text();
if (!text)
return true;
return !text->length();
@@ -1366,116 +1011,154 @@ UChar RenderText::previousCharacter() const
// find previous text renderer if one exists
const RenderObject* previousText = this;
while ((previousText = previousText->previousInPreOrder()))
- if (!isInlineFlowOrEmptyText(previousText))
+ if (!isInlineFlowOrEmptyText(*previousText))
break;
UChar prev = ' ';
- if (previousText && previousText->isText())
- if (StringImpl* previousString = toRenderText(previousText)->text())
+ if (is<RenderText>(previousText)) {
+ if (StringImpl* previousString = downcast<RenderText>(*previousText).text())
prev = (*previousString)[previousString->length() - 1];
+ }
return prev;
}
-void applyTextTransform(const RenderStyle* style, String& text, UChar previousCharacter)
+LayoutUnit RenderText::topOfFirstText() const
{
- if (!style)
- return;
+ return firstTextBox()->root().lineTop();
+}
- switch (style->textTransform()) {
+void applyTextTransform(const RenderStyle& style, String& text, UChar previousCharacter)
+{
+ switch (style.textTransform()) {
case TTNONE:
break;
case CAPITALIZE:
makeCapitalized(&text, previousCharacter);
break;
case UPPERCASE:
- text.makeUpper();
+ text = text.convertToUppercaseWithLocale(style.locale());
break;
case LOWERCASE:
- text.makeLower();
+ text = text.convertToLowercaseWithLocale(style.locale());
break;
}
}
-void RenderText::setTextInternal(PassRefPtr<StringImpl> text)
+void RenderText::setRenderedText(const String& text)
{
- ASSERT(text);
+ ASSERT(!text.isNull());
+
+ String originalText = this->originalText();
+
m_text = text;
- if (m_needsTranscoding) {
- const TextEncoding* encoding = document()->decoder() ? &document()->decoder()->encoding() : 0;
- fontTranscoder().convert(m_text, style()->font().fontDescription(), encoding);
- }
+
+ if (m_useBackslashAsYenSymbol)
+ m_text.replace('\\', yenSign);
+
ASSERT(m_text);
- if (style()) {
- applyTextTransform(style(), m_text, previousCharacter());
+ applyTextTransform(style(), m_text, previousCharacter());
- // 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:
- secureText(whiteBullet);
- break;
- case TSDISC:
- secureText(bullet);
- break;
- case TSSQUARE:
- secureText(blackSquare);
- }
+ switch (style().textSecurity()) {
+ case TSNONE:
+ break;
+#if !PLATFORM(IOS)
+ // We use the same characters here as for list markers.
+ // See the listMarkerText function in RenderListMarker.cpp.
+ case TSCIRCLE:
+ secureText(whiteBullet);
+ break;
+ case TSDISC:
+ secureText(bullet);
+ break;
+ case TSSQUARE:
+ secureText(blackSquare);
+ break;
+#else
+ // FIXME: Why this quirk on iOS?
+ case TSCIRCLE:
+ case TSDISC:
+ case TSSQUARE:
+ secureText(blackCircle);
+ break;
+#endif
}
- ASSERT(m_text);
- ASSERT(!isBR() || (textLength() == 1 && m_text[0] == '\n'));
+ ASSERT(!m_text.isNull());
m_isAllASCII = m_text.containsOnlyASCII();
m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
+
+ if (m_text != originalText) {
+ originalTextMap().set(this, originalText);
+ m_originalTextDiffersFromRendered = true;
+ } else if (m_originalTextDiffersFromRendered) {
+ originalTextMap().remove(this);
+ m_originalTextDiffersFromRendered = false;
+ }
}
-void RenderText::secureText(UChar mask)
+void RenderText::secureText(UChar maskingCharacter)
{
- if (!m_text.length())
+ // This hides the text by replacing all the characters with the masking character.
+ // Offsets within the hidden text have to match offsets within the original text
+ // to handle things like carets and selection, so this won't work right if any
+ // of the characters are surrogate pairs or combining marks. Thus, this function
+ // does not attempt to handle either of those.
+
+ unsigned length = textLength();
+ if (!length)
return;
- int lastTypedCharacterOffsetToReveal = -1;
- String revealedText;
- SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->get(this) : 0;
- if (secureTextTimer && secureTextTimer->isActive()) {
- lastTypedCharacterOffsetToReveal = secureTextTimer->lastTypedCharacterOffset();
- if (lastTypedCharacterOffsetToReveal >= 0)
- revealedText.append(m_text[lastTypedCharacterOffsetToReveal]);
- }
+ UChar characterToReveal = 0;
+ unsigned revealedCharactersOffset;
- m_text.fill(mask);
- if (lastTypedCharacterOffsetToReveal >= 0) {
- m_text.replace(lastTypedCharacterOffsetToReveal, 1, revealedText);
- // m_text may be updated later before timer fires. We invalidate the lastTypedCharacterOffset to avoid inconsistency.
- secureTextTimer->invalidate();
+ if (SecureTextTimer* timer = secureTextTimers().get(this)) {
+ // We take the offset out of the timer to make this one-shot. We count on this being called only once.
+ // If it's called a second time we assume the text is different and a character should not be revealed.
+ revealedCharactersOffset = timer->takeOffsetAfterLastTypedCharacter();
+ if (revealedCharactersOffset && revealedCharactersOffset <= length)
+ characterToReveal = m_text[--revealedCharactersOffset];
}
+
+ UChar* characters;
+ m_text = String::createUninitialized(length, characters);
+
+ for (unsigned i = 0; i < length; ++i)
+ characters[i] = maskingCharacter;
+ if (characterToReveal)
+ characters[revealedCharactersOffset] = characterToReveal;
}
-void RenderText::setText(PassRefPtr<StringImpl> text, bool force)
+void RenderText::setText(const String& text, bool force)
{
- ASSERT(text);
+ ASSERT(!text.isNull());
- if (!force && equal(m_text.impl(), text.get()))
+ if (!force && text == originalText())
return;
- setTextInternal(text);
+ m_text = text;
+ if (m_originalTextDiffersFromRendered) {
+ originalTextMap().remove(this);
+ m_originalTextDiffersFromRendered = false;
+ }
+
+ setRenderedText(text);
+
setNeedsLayoutAndPrefWidthsRecalc();
m_knownToHaveNoOverflowAndNoFallbackFonts = false;
+
+ if (is<RenderBlockFlow>(*parent()))
+ downcast<RenderBlockFlow>(*parent()).invalidateLineLayoutPath();
- if (AXObjectCache* cache = document()->existingAXObjectCache())
+ if (AXObjectCache* cache = document().existingAXObjectCache())
cache->textChanged(this);
}
-String RenderText::textWithoutTranscoding() const
+String RenderText::textWithoutConvertingBackslashToYenSymbol() const
{
- // If m_text isn't transcoded or is secure, we can just return the modified text.
- if (!m_needsTranscoding || style()->textSecurity() != TSNONE)
+ if (!m_useBackslashAsYenSymbol || style().textSecurity() != TSNONE)
return text();
- // Otherwise, we should use original text. If text-transform is
- // specified, we should transform the text on the fly.
String text = originalText();
applyTextTransform(style(), text, previousCharacter());
return text;
@@ -1484,57 +1167,39 @@ String RenderText::textWithoutTranscoding() const
void RenderText::dirtyLineBoxes(bool fullLayout)
{
if (fullLayout)
- deleteTextBoxes();
- else if (!m_linesDirty) {
- for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
- box->dirtyLineBoxes();
- }
+ m_lineBoxes.deleteAll();
+ else if (!m_linesDirty)
+ m_lineBoxes.dirtyAll();
m_linesDirty = false;
}
-InlineTextBox* RenderText::createTextBox()
+std::unique_ptr<InlineTextBox> RenderText::createTextBox()
{
- return new (renderArena()) InlineTextBox(this);
+ return std::make_unique<InlineTextBox>(*this);
}
-InlineTextBox* RenderText::createInlineTextBox()
+void RenderText::positionLineBox(InlineTextBox& textBox)
{
- InlineTextBox* textBox = createTextBox();
- if (!m_firstTextBox)
- m_firstTextBox = m_lastTextBox = textBox;
- else {
- m_lastTextBox->setNextTextBox(textBox);
- textBox->setPreviousTextBox(m_lastTextBox);
- m_lastTextBox = textBox;
- }
- textBox->setIsText(true);
- return textBox;
+ if (!textBox.len())
+ return;
+ m_containsReversedText |= !textBox.isLeftToRightDirection();
}
-void RenderText::positionLineBox(InlineBox* box)
+void RenderText::ensureLineBoxes()
{
- InlineTextBox* s = toInlineTextBox(box);
-
- // FIXME: should not be needed!!!
- if (!s->len()) {
- // We want the box to be destroyed.
- s->remove();
- 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(renderArena());
+ if (!is<RenderBlockFlow>(*parent()))
return;
- }
+ downcast<RenderBlockFlow>(*parent()).ensureLineBoxes();
+}
- m_containsReversedText |= !s->isLeftToRightDirection();
+const SimpleLineLayout::Layout* RenderText::simpleLineLayout() const
+{
+ if (!is<RenderBlockFlow>(*parent()))
+ return nullptr;
+ return downcast<RenderBlockFlow>(*parent()).simpleLineLayout();
}
-float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
+float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
if (from >= textLength())
return 0;
@@ -1542,18 +1207,20 @@ float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine,
if (from + len > textLength())
len = textLength() - from;
- return width(from, len, style(firstLine)->font(), xPos, fallbackFonts, glyphOverflow);
+ const RenderStyle& lineStyle = firstLine ? firstLineStyle() : style();
+ return width(from, len, lineStyle.fontCascade(), xPos, fallbackFonts, glyphOverflow);
}
-float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
+float RenderText::width(unsigned from, unsigned len, const FontCascade& f, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
{
ASSERT(from + len <= textLength());
if (!textLength())
return 0;
+ const RenderStyle& style = this->style();
float w;
- if (&f == &style()->font()) {
- if (!style()->preserveNewline() && !from && len == textLength() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
+ if (&f == &style.fontCascade()) {
+ if (!style.preserveNewline() && !from && len == textLength() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
if (fallbackFonts) {
ASSERT(glyphOverflow);
if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts) {
@@ -1565,14 +1232,14 @@ float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
} else
w = maxLogicalWidth();
} else
- w = widthFromCache(f, from, len, xPos, fallbackFonts, glyphOverflow);
+ w = widthFromCache(f, from, len, xPos, fallbackFonts, glyphOverflow, style);
} else {
- TextRun run = RenderBlock::constructTextRun(const_cast<RenderText*>(this), f, this, from, len, style());
+ TextRun run = RenderBlock::constructTextRun(const_cast<RenderText*>(this), f, this, from, len, style);
run.setCharactersLength(textLength() - from);
ASSERT(run.charactersLength() >= run.length());
run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
- run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize());
+ run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
run.setXPos(xPos);
w = f.width(run, fallbackFonts, glyphOverflow);
}
@@ -1582,53 +1249,16 @@ float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
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();
- float y = isHorizontal ? firstTextBox()->y() : logicalLeftSide;
- float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastTextBox()->logicalBottom() - x;
- float height = isHorizontal ? lastTextBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide;
- result = enclosingIntRect(FloatRect(x, y, width, height));
- }
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::computeBoundingBox(*this, *layout);
- return result;
+ return m_lineBoxes.boundingBox(*this);
}
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()) {
- logicalLeftSide = min(logicalLeftSide, curr->logicalLeftVisualOverflow());
- logicalRightSide = max(logicalRightSide, curr->logicalRightVisualOverflow());
- }
-
- 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;
+ ASSERT(!simpleLineLayout());
+ return m_lineBoxes.visualOverflowBoundingBox(*this);
}
LayoutRect RenderText::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const
@@ -1636,9 +1266,9 @@ LayoutRect RenderText::clippedOverflowRectForRepaint(const RenderLayerModelObjec
RenderObject* rendererToRepaint = containingBlock();
// Do not cross self-painting layer boundaries.
- RenderObject* enclosingLayerRenderer = enclosingLayer()->renderer();
- if (enclosingLayerRenderer != rendererToRepaint && !rendererToRepaint->isDescendantOf(enclosingLayerRenderer))
- rendererToRepaint = enclosingLayerRenderer;
+ RenderObject& enclosingLayerRenderer = enclosingLayer()->renderer();
+ if (&enclosingLayerRenderer != rendererToRepaint && !rendererToRepaint->isDescendantOf(&enclosingLayerRenderer))
+ rendererToRepaint = &enclosingLayerRenderer;
// The renderer we chose to repaint may be an ancestor of repaintContainer, but we need to do a repaintContainer-relative repaint.
if (repaintContainer && repaintContainer != rendererToRepaint && !rendererToRepaint->isDescendantOf(repaintContainer))
@@ -1647,9 +1277,10 @@ LayoutRect RenderText::clippedOverflowRectForRepaint(const RenderLayerModelObjec
return rendererToRepaint->clippedOverflowRectForRepaint(repaintContainer);
}
-LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent)
+LayoutRect RenderText::collectSelectionRectsForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<LayoutRect>* rects)
{
ASSERT(!needsLayout());
+ ASSERT(!simpleLineLayout());
if (selectionState() == SelectionNone)
return LayoutRect();
@@ -1675,53 +1306,70 @@ LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* rep
if (startPos == endPos)
return IntRect();
- LayoutRect rect;
- for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
- rect.unite(box->localSelectionRect(startPos, endPos));
- rect.unite(ellipsisRectForBox(box, startPos, endPos));
+ LayoutRect resultRect;
+ if (!rects)
+ resultRect = m_lineBoxes.selectionRectForRange(startPos, endPos);
+ else {
+ m_lineBoxes.collectSelectionRectsForRange(startPos, endPos, *rects);
+ for (auto& rect : *rects) {
+ resultRect.unite(rect);
+ rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
+ }
}
if (clipToVisibleContent)
- computeRectForRepaint(repaintContainer, rect);
- else {
- if (cb->hasColumns())
- cb->adjustRectForColumns(rect);
+ return computeRectForRepaint(resultRect, repaintContainer);
+ return localToContainerQuad(FloatRect(resultRect), repaintContainer).enclosingBoundingBox();
+}
- rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
- }
+LayoutRect RenderText::collectSelectionRectsForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<LayoutRect>& rects)
+{
+ return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, &rects);
+}
- return rect;
+LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent)
+{
+ return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, nullptr);
}
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;
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::findCaretMinimumOffset(*this, *layout);
+ return m_lineBoxes.caretMinOffset();
}
int RenderText::caretMaxOffset() const
{
- InlineTextBox* box = lastTextBox();
- if (!lastTextBox())
- return textLength();
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::findCaretMaximumOffset(*this, *layout);
+ return m_lineBoxes.caretMaxOffset(*this);
+}
+
+unsigned RenderText::countRenderedCharacterOffsetsUntil(unsigned offset) const
+{
+ ASSERT(!simpleLineLayout());
+ return m_lineBoxes.countCharacterOffsetsUntil(offset);
+}
+
+bool RenderText::containsRenderedCharacterOffset(unsigned offset) const
+{
+ ASSERT(!simpleLineLayout());
+ return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CharacterOffset);
+}
- int maxOffset = box->start() + box->len();
- for (box = box->prevTextBox(); box; box = box->prevTextBox())
- maxOffset = max<int>(maxOffset, box->start() + box->len());
- return maxOffset;
+bool RenderText::containsCaretOffset(unsigned offset) const
+{
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::containsCaretOffset(*this, *layout, offset);
+ return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CaretOffset);
}
-unsigned RenderText::renderedTextLength() const
+bool RenderText::hasRenderedText() const
{
- int l = 0;
- for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox())
- l += box->len();
- return l;
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::isTextRendered(*this, *layout);
+ return m_lineBoxes.hasRenderedText();
}
int RenderText::previousOffset(int current) const
@@ -1730,7 +1378,7 @@ int RenderText::previousOffset(int current) const
return current - 1;
StringImpl* textImpl = m_text.impl();
- TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(), textImpl->length());
+ TextBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
if (!iterator)
return current - 1;
@@ -1742,60 +1390,81 @@ int RenderText::previousOffset(int current) const
return result;
}
-#if PLATFORM(MAC)
-
-#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
-};
+#if PLATFORM(COCOA) || PLATFORM(EFL) || PLATFORM(GTK)
-inline bool isHangulLVT(UChar32 character)
+const UChar hangulChoseongStart = 0x1100;
+const UChar hangulChoseongEnd = 0x115F;
+const UChar hangulJungseongStart = 0x1160;
+const UChar hangulJungseongEnd = 0x11A2;
+const UChar hangulJongseongStart = 0x11A8;
+const UChar hangulJongseongEnd = 0x11F9;
+const UChar hangulSyllableStart = 0xAC00;
+const UChar hangulSyllableEnd = 0xD7AF;
+const UChar hangulJongseongCount = 28;
+
+enum class HangulState { L, V, T, LV, LVT, Break };
+
+static inline bool isHangulLVT(UChar character)
{
- return (character - HANGUL_SYLLABLE_START) % HANGUL_JONGSEONG_COUNT;
+ return (character - hangulSyllableStart) % hangulJongseongCount;
}
-inline bool isMark(UChar32 c)
+static inline bool isMark(UChar32 character)
{
- int8_t charType = u_charType(c);
- return charType == U_NON_SPACING_MARK || charType == U_ENCLOSING_MARK || charType == U_COMBINING_SPACING_MARK;
+ return U_GET_GC_MASK(character) & U_GC_M_MASK;
}
-inline bool isRegionalIndicator(UChar32 c)
+static inline bool isRegionalIndicator(UChar32 character)
{
// National flag emoji each consists of a pair of regional indicator symbols.
- return 0x1F1E6 <= c && c <= 0x1F1FF;
+ return 0x1F1E6 <= character && character <= 0x1F1FF;
+}
+
+static inline bool isInArmenianToLimbuRange(UChar32 character)
+{
+ return character >= 0x0530 && character < 0x1950;
}
#endif
int RenderText::previousOffsetForBackwardDeletion(int current) const
{
-#if PLATFORM(MAC)
- ASSERT(m_text);
+ ASSERT(!m_text.isNull());
StringImpl& text = *m_text.impl();
- UChar32 character;
+
+ // FIXME: Unclear why this has so much handrolled code rather than using TextBreakIterator.
+ // Also unclear why this is so different from advanceByCombiningCharacterSequence.
+
+ // FIXME: Seems like this fancier case could be used on all platforms now, no
+ // need for the #else case below.
+#if PLATFORM(COCOA) || PLATFORM(EFL) || PLATFORM(GTK)
bool sawRegionalIndicator = false;
+ bool sawEmojiGroupCandidate = false;
+ bool sawEmojiModifier = false;
+
while (current > 0) {
- if (U16_IS_TRAIL(text[--current]))
- --current;
- if (current < 0)
+ UChar32 character;
+ U16_PREV(text, 0, current, character);
+
+ if (sawEmojiGroupCandidate) {
+ sawEmojiGroupCandidate = false;
+ if (character == zeroWidthJoiner)
+ continue;
+ // We could have two emoji group candidates without a joiner in between.
+ // Those should not be treated as a group.
+ U16_FWD_1_UNSAFE(text, current);
break;
+ }
- UChar32 character = text.characterStartingAt(current);
+ if (sawEmojiModifier) {
+ if (isEmojiModifier(character)) {
+ // Don't treat two emoji modifiers in a row as a group.
+ U16_FWD_1_UNSAFE(text, current);
+ break;
+ }
+ if (!isVariationSelector(character))
+ break;
+ }
if (sawRegionalIndicator) {
// We don't check if the pair of regional indicator symbols before current position can actually be combined
@@ -1808,15 +1477,26 @@ int RenderText::previousOffsetForBackwardDeletion(int current) const
}
// We don't combine characters in Armenian ... Limbu range for backward deletion.
- if ((character >= 0x0530) && (character < 0x1950))
+ if (isInArmenianToLimbuRange(character))
break;
if (isRegionalIndicator(character)) {
sawRegionalIndicator = true;
continue;
}
+
+ if (isEmojiModifier(character)) {
+ sawEmojiModifier = true;
+ continue;
+ }
- if (!isMark(character) && (character != 0xFF9E) && (character != 0xFF9F))
+ if (isEmojiGroupCandidate(character)) {
+ sawEmojiGroupCandidate = true;
+ continue;
+ }
+
+ // FIXME: Why are FF9E and FF9F special cased here?
+ if (!isMark(character) && character != 0xFF9E && character != 0xFF9F)
break;
}
@@ -1824,58 +1504,50 @@ int RenderText::previousOffsetForBackwardDeletion(int current) const
return current;
// Hangul
- character = text.characterStartingAt(current);
- if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_END))) {
+ UChar character = text[current];
+ if ((character >= hangulChoseongStart && character <= hangulJongseongEnd) || (character >= hangulSyllableStart && character <= hangulSyllableEnd)) {
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;
+ if (character < hangulJungseongStart)
+ state = HangulState::L;
+ else if (character < hangulJongseongStart)
+ state = HangulState::V;
+ else if (character < hangulSyllableStart)
+ state = HangulState::T;
+ else
+ state = isHangulLVT(character) ? HangulState::LVT : HangulState::LV;
- while (current > 0 && ((character = text.characterStartingAt(current - 1)) >= HANGUL_CHOSEONG_START) && (character <= HANGUL_SYLLABLE_END) && ((character <= HANGUL_JONGSEONG_END) || (character >= HANGUL_SYLLABLE_START))) {
+ while (current > 0 && (character = text[current - 1]) >= hangulChoseongStart && character <= hangulSyllableEnd && (character <= hangulJongseongEnd || character >= hangulSyllableStart)) {
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;
+ case HangulState::V:
+ if (character <= hangulChoseongEnd)
+ state = HangulState::L;
+ else if (character >= hangulSyllableStart && character <= hangulSyllableEnd && !isHangulLVT(character))
+ state = HangulState::LV;
+ else if (character > hangulJungseongEnd)
+ state = HangulState::Break;
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;
+ case HangulState::T:
+ if (character >= hangulJungseongStart && character <= hangulJungseongEnd)
+ state = HangulState::V;
+ else if (character >= hangulSyllableStart && character <= hangulSyllableEnd)
+ state = isHangulLVT(character) ? HangulState::LVT : HangulState::LV;
+ else if (character < hangulJungseongStart)
+ state = HangulState::Break;
break;
default:
- state = (character < HANGUL_JUNGSEONG_START) ? HangulStateL : HangulStateBreak;
+ state = (character < hangulJungseongStart) ? HangulState::L : HangulState::Break;
break;
}
- if (state == HangulStateBreak)
+ if (state == HangulState::Break)
break;
-
--current;
}
}
return current;
#else
- // Platforms other than Mac delete by one code point.
- if (U16_IS_TRAIL(m_text[--current]))
- --current;
- if (current < 0)
- current = 0;
+ U16_BACK_1(text, 0, current);
return current;
#endif
}
@@ -1886,7 +1558,7 @@ int RenderText::nextOffset(int current) const
return current + 1;
StringImpl* textImpl = m_text.impl();
- TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(), textImpl->length());
+ TextBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
if (!iterator)
return current + 1;
@@ -1901,37 +1573,31 @@ bool RenderText::computeCanUseSimpleFontCodePath() const
{
if (isAllASCII() || m_text.is8Bit())
return true;
- return Font::characterRangeCodePath(characters(), length()) == Font::Simple;
+ return FontCascade::characterRangeCodePath(characters16(), length()) == FontCascade::Simple;
}
-#ifndef NDEBUG
-
-void RenderText::checkConsistency() const
+void RenderText::momentarilyRevealLastTypedCharacter(unsigned offsetAfterLastTypedCharacter)
{
-#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);
+ if (style().textSecurity() == TSNONE)
+ return;
+ auto& secureTextTimer = secureTextTimers().add(this, nullptr).iterator->value;
+ if (!secureTextTimer)
+ secureTextTimer = std::make_unique<SecureTextTimer>(*this);
+ secureTextTimer->restart(offsetAfterLastTypedCharacter);
+}
+
+StringView RenderText::stringView(int start, int stop) const
+{
+ if (stop == -1)
+ stop = textLength();
+ ASSERT(static_cast<unsigned>(start) <= length());
+ ASSERT(static_cast<unsigned>(stop) <= length());
+ ASSERT(start <= stop);
+ ASSERT(start >= 0);
+ ASSERT(stop >= 0);
+ if (is8Bit())
+ return StringView(characters8() + start, stop - start);
+ return StringView(characters16() + start, stop - start);
}
} // namespace WebCore