summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/RenderText.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/rendering/RenderText.cpp')
-rw-r--r--Source/WebCore/rendering/RenderText.cpp888
1 files changed, 546 insertions, 342 deletions
diff --git a/Source/WebCore/rendering/RenderText.cpp b/Source/WebCore/rendering/RenderText.cpp
index 527fb33c9..333d216bf 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, 2013 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,6 +26,9 @@
#include "RenderText.h"
#include "AXObjectCache.h"
+#include "BreakLines.h"
+#include "BreakingContext.h"
+#include "CharacterProperties.h"
#include "EllipsisBox.h"
#include "FloatQuad.h"
#include "Frame.h"
@@ -35,17 +38,18 @@
#include "Range.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 <wtf/text/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>
#if PLATFORM(IOS)
@@ -63,7 +67,7 @@ namespace WebCore {
struct SameSizeAsRenderText : public RenderObject {
uint32_t bitfields : 16;
-#if ENABLE(IOS_TEXT_AUTOSIZING)
+#if ENABLE(TEXT_AUTOSIZING)
float candidateTextSize;
#endif
float widths[4];
@@ -73,45 +77,60 @@ 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;
- const Settings& settings = m_renderText->frame().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;
+ void fired() override;
+ RenderText& m_renderer;
+ unsigned m_offsetAfterLastTypedCharacter { 0 };
};
+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.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;
}
-static void makeCapitalized(String* string, UChar previous)
+void makeCapitalized(String* string, UChar previous)
{
// FIXME: Need to change this to use u_strToTitle instead of u_totitle and to consider locale.
@@ -134,7 +153,7 @@ static void makeCapitalized(String* string, UChar previous)
stringWithPrevious[i] = stringImpl[i - 1];
}
- TextBreakIterator* boundary = wordBreakIterator(StringView(stringWithPrevious.characters(), length + 1));
+ UBreakIterator* boundary = wordBreakIterator(StringView(stringWithPrevious.characters(), length + 1));
if (!boundary)
return;
@@ -142,8 +161,8 @@ static void makeCapitalized(String* string, UChar previous)
result.reserveCapacity(length);
int32_t endOfWord;
- int32_t startOfWord = textBreakFirst(boundary);
- for (endOfWord = textBreakNext(boundary); endOfWord != TextBreakDone; startOfWord = endOfWord, endOfWord = textBreakNext(boundary)) {
+ int32_t startOfWord = ubrk_first(boundary);
+ for (endOfWord = ubrk_next(boundary); endOfWord != UBRK_DONE; startOfWord = endOfWord, endOfWord = ubrk_next(boundary)) {
if (startOfWord) // Ignore first char of previous string
result.append(stringImpl[startOfWord - 1] == noBreakSpace ? noBreakSpace : u_totitle(stringWithPrevious[startOfWord]));
for (int i = startOfWord + 1; i < endOfWord; i++)
@@ -153,8 +172,8 @@ static void makeCapitalized(String* string, UChar previous)
*string = result.toString();
}
-RenderText::RenderText(Text& textNode, const String& text)
- : RenderObject(textNode)
+inline RenderText::RenderText(Node& node, const String& text)
+ : RenderObject(node)
, m_hasTab(false)
, m_linesDirty(false)
, m_containsReversedText(false)
@@ -162,7 +181,7 @@ RenderText::RenderText(Text& textNode, const String& text)
, m_knownToHaveNoOverflowAndNoFallbackFonts(false)
, m_useBackslashAsYenSymbol(false)
, m_originalTextDiffersFromRendered(false)
-#if ENABLE(IOS_TEXT_AUTOSIZING)
+#if ENABLE(TEXT_AUTOSIZING)
, m_candidateComputedTextSize(0)
#endif
, m_minWidth(-1)
@@ -177,34 +196,20 @@ RenderText::RenderText(Text& textNode, const String& text)
view().frameView().incrementVisuallyNonEmptyCharacterCount(textLength());
}
+RenderText::RenderText(Text& textNode, const String& text)
+ : RenderText(static_cast<Node&>(textNode), text)
+{
+}
+
RenderText::RenderText(Document& document, const String& text)
- : RenderObject(document)
- , m_hasTab(false)
- , m_linesDirty(false)
- , m_containsReversedText(false)
- , m_isAllASCII(text.containsOnlyASCII())
- , m_knownToHaveNoOverflowAndNoFallbackFonts(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(text)
+ : RenderText(static_cast<Node&>(document), text)
{
- ASSERT(!m_text.isNull());
- setIsText();
- m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
- view().frameView().incrementVisuallyNonEmptyCharacterCount(textLength());
}
RenderText::~RenderText()
{
- if (m_originalTextDiffersFromRendered)
- originalTextMap().remove(this);
+ // Do not add any code here. Add it to willBeDestroyed() instead.
+ ASSERT(!originalTextMap().contains(this));
}
const char* RenderText::renderName() const
@@ -214,7 +219,7 @@ const char* RenderText::renderName() const
Text* RenderText::textNode() const
{
- return toText(RenderObject::node());
+ return downcast<Text>(RenderObject::node());
}
bool RenderText::isTextFragment() const
@@ -225,8 +230,8 @@ bool RenderText::isTextFragment() const
bool RenderText::computeUseBackslashAsYenSymbol() const
{
const RenderStyle& style = this->style();
- const FontDescription& fontDescription = style.font().fontDescription();
- if (style.font().useBackslashAsYenSymbol())
+ const auto& fontDescription = style.fontDescription();
+ if (style.fontCascade().useBackslashAsYenSymbol())
return true;
if (fontDescription.isSpecifiedFont())
return false;
@@ -252,7 +257,9 @@ void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyl
if (!oldStyle) {
m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
needsResetText = m_useBackslashAsYenSymbol;
- } else if (oldStyle->font().useBackslashAsYenSymbol() != newStyle.font().useBackslashAsYenSymbol()) {
+ // It should really be computed in the c'tor, but during construction we don't have parent yet -and RenderText style == parent()->style()
+ m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
+ } else if (oldStyle->fontCascade().useBackslashAsYenSymbol() != newStyle.fontCascade().useBackslashAsYenSymbol()) {
m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
needsResetText = true;
}
@@ -265,7 +272,7 @@ void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyl
void RenderText::removeAndDestroyTextBoxes()
{
- if (!documentBeingDestroyed())
+ if (!renderTreeBeingDestroyed())
m_lineBoxes.removeAllFromParent(*this);
#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
else
@@ -276,10 +283,13 @@ void RenderText::removeAndDestroyTextBoxes()
void RenderText::willBeDestroyed()
{
- if (SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->take(this) : 0)
- delete secureTextTimer;
+ secureTextTimers().remove(this);
removeAndDestroyTextBoxes();
+
+ if (m_originalTextDiffersFromRendered)
+ originalTextMap().remove(this);
+
RenderObject::willBeDestroyed();
}
@@ -295,8 +305,8 @@ String RenderText::originalText() const
void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
{
- if (auto layout = simpleLineLayout()) {
- rects.appendVector(collectTextAbsoluteRects(*this, *layout, accumulatedOffset));
+ if (auto* layout = simpleLineLayout()) {
+ rects.appendVector(SimpleLineLayout::collectAbsoluteRects(*this, *layout, accumulatedOffset));
return;
}
rects.appendVector(m_lineBoxes.absoluteRects(accumulatedOffset));
@@ -379,8 +389,8 @@ void RenderText::collectSelectionRects(Vector<SelectionRect>& rects, unsigned st
bool containsEnd = box->start() <= end && box->end() + 1 >= end;
bool isFixed = false;
- IntRect absRect = localToAbsoluteQuad(FloatRect(rect), false, &isFixed).enclosingBoundingBox();
- bool boxIsHorizontal = !box->isSVGInlineTextBox() ? box->isHorizontal() : !style().svgStyle().isVerticalWritingMode();
+ IntRect absRect = localToAbsoluteQuad(FloatRect(rect), UseTransforms, &isFixed).enclosingBoundingBox();
+ bool boxIsHorizontal = !box->isSVGInlineTextBox() ? box->isHorizontal() : !style().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.
@@ -391,24 +401,24 @@ void RenderText::collectSelectionRects(Vector<SelectionRect>& rects, unsigned st
}
}
- rects.append(SelectionRect(absRect, box->direction(), extentsRect.x(), extentsRect.maxX(), extentsRect.maxY(), 0, box->isLineBreak(), isFirstOnLine, isLastOnLine, containsStart, containsEnd, boxIsHorizontal, isFixed, containingBlock->isRubyText(), columnNumberForOffset(absRect.x())));
+ 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())));
}
}
#endif
Vector<FloatQuad> RenderText::absoluteQuadsClippedToEllipsis() const
{
- if (auto layout = simpleLineLayout()) {
+ if (auto* layout = simpleLineLayout()) {
ASSERT(style().textOverflow() != TextOverflowEllipsis);
- return collectTextAbsoluteQuads(*this, *layout, nullptr);
+ return SimpleLineLayout::collectAbsoluteQuads(*this, *layout, nullptr);
}
return m_lineBoxes.absoluteQuads(*this, nullptr, RenderTextLineBoxes::ClipToEllipsis);
}
void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
{
- if (auto layout = simpleLineLayout()) {
- quads.appendVector(collectTextAbsoluteQuads(*this, *layout, wasFixed));
+ if (auto* layout = simpleLineLayout()) {
+ quads.appendVector(SimpleLineLayout::collectAbsoluteQuads(*this, *layout, wasFixed));
return;
}
quads.appendVector(m_lineBoxes.absoluteQuads(*this, wasFixed, RenderTextLineBoxes::NoClipping));
@@ -416,53 +426,62 @@ void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
Vector<FloatQuad> RenderText::absoluteQuadsForRange(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
- // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
+ // 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));
-
+ if (simpleLineLayout() && !useSelectionHeight)
+ return collectAbsoluteQuadsForRange(*this, start, end, *simpleLineLayout(), wasFixed);
+ const_cast<RenderText&>(*this).ensureLineBoxes();
return m_lineBoxes.absoluteQuadsForRange(*this, start, end, useSelectionHeight, wasFixed);
}
-VisiblePosition RenderText::positionForPoint(const LayoutPoint& point)
+Position RenderText::positionForPoint(const LayoutPoint& point)
{
- ensureLineBoxes();
+ if (simpleLineLayout() && parent()->firstChild() == parent()->lastChild()) {
+ auto position = Position(textNode(), SimpleLineLayout::textOffsetForPoint(point, *this, *simpleLineLayout()));
+ ASSERT(position == positionForPoint(point, nullptr).deepEquivalent());
+ return position;
+ }
+ return positionForPoint(point, nullptr).deepEquivalent();
+}
+VisiblePosition RenderText::positionForPoint(const LayoutPoint& point, const RenderRegion*)
+{
+ ensureLineBoxes();
return m_lineBoxes.positionForPoint(*this, point);
}
-LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, LayoutUnit* extraWidthToEndOfLine)
+LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, unsigned caretOffset, LayoutUnit* extraWidthToEndOfLine)
{
if (!inlineBox)
return LayoutRect();
- InlineTextBox* box = toInlineTextBox(inlineBox);
- float left = box->positionForOffset(caretOffset);
- return box->root().computeCaretRect(left, caretWidth, extraWidthToEndOfLine);
+ 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 RenderStyle& style) const
+ALWAYS_INLINE float RenderText::widthFromCache(const FontCascade& f, unsigned start, unsigned len, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow, const RenderStyle& style) const
{
- if (style.hasTextCombine() && isCombineText()) {
- const RenderCombineText& combineText = toRenderCombineText(*this);
+ 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;
ASSERT(m_text);
StringImpl& text = *m_text.impl();
- for (int i = start; i < start + len; i++) {
+ for (unsigned i = start; i < start + len; i++) {
char c = text[i];
if (c <= ' ') {
if (c == ' ' || c == '\n') {
@@ -488,7 +507,7 @@ 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(*this, start, len, style);
run.setCharactersLength(textLength() - start);
ASSERT(run.charactersLength() >= run.length());
@@ -498,6 +517,94 @@ ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len
return f.width(run, fallbackFonts, glyphOverflow);
}
+inline bool isHangablePunctuationAtLineStart(UChar c)
+{
+ return U_GET_GC_MASK(c) & (U_GC_PS_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
+}
+
+inline bool isHangablePunctuationAtLineEnd(UChar c)
+{
+ return U_GET_GC_MASK(c) & (U_GC_PE_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
+}
+
+float RenderText::hangablePunctuationStartWidth(unsigned index) const
+{
+ unsigned len = textLength();
+ if (!len || index >= len)
+ return 0;
+
+ ASSERT(m_text);
+ StringImpl& text = *m_text.impl();
+
+ if (!isHangablePunctuationAtLineStart(text[index]))
+ return 0;
+
+ const RenderStyle& style = this->style();
+ const FontCascade& font = style.fontCascade();
+
+ return widthFromCache(font, index, 1, 0, 0, 0, style);
+}
+
+float RenderText::hangablePunctuationEndWidth(unsigned index) const
+{
+ unsigned len = textLength();
+ if (!len || index >= len)
+ return 0;
+
+ ASSERT(m_text);
+ StringImpl& text = *m_text.impl();
+
+ if (!isHangablePunctuationAtLineEnd(text[index]))
+ return 0;
+
+ const RenderStyle& style = this->style();
+ const FontCascade& font = style.fontCascade();
+
+ return widthFromCache(font, index, 1, 0, 0, 0, style);
+}
+
+bool RenderText::isHangableStopOrComma(UChar c) const
+{
+ return c == 0x002C || c == 0x002E || c == 0x060C || c == 0x06D4 || c == 0x3001
+ || c == 0x3002 || c == 0xFF0C || c == 0xFF0E || c == 0xFE50 || c == 0xFE51
+ || c == 0xFE52 || c == 0xFF61 || c == 0xFF64;
+}
+
+unsigned RenderText::firstCharacterIndexStrippingSpaces() const
+{
+ if (!style().collapseWhiteSpace())
+ return 0;
+
+ ASSERT(m_text);
+ StringImpl& text = *m_text.impl();
+
+ unsigned i = 0;
+ for ( ; i < textLength(); ++i) {
+ if (text[i] != ' ' && (text[i] != '\n' || style().preserveNewline()) && text[i] != '\t')
+ break;
+ }
+ return i;
+}
+
+unsigned RenderText::lastCharacterIndexStrippingSpaces() const
+{
+ if (!textLength())
+ return 0;
+
+ if (!style().collapseWhiteSpace())
+ return textLength() - 1;
+
+ ASSERT(m_text);
+ StringImpl& text = *m_text.impl();
+
+ int i = textLength() - 1;
+ for ( ; i >= 0; --i) {
+ if (text[i] != ' ' && (text[i] != '\n' || style().preserveNewline()) && text[i] != '\t')
+ break;
+ }
+ return i;
+}
+
void RenderText::trimmedPrefWidths(float leadWidth,
float& beginMinW, bool& beginWS,
float& endMinW, bool& endWS,
@@ -516,7 +623,7 @@ void RenderText::trimmedPrefWidths(float leadWidth,
beginWS = !stripFrontSpaces && m_hasBeginWS;
endWS = m_hasEndWS;
- int len = textLength();
+ unsigned len = textLength();
if (!len || (stripFrontSpaces && text()->containsOnlyWhitespace())) {
beginMinW = 0;
@@ -540,11 +647,10 @@ 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] == space || (text[0] == newlineCharacter && !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(&space, 1, style));
maxW -= spaceWidth;
} else
maxW += font.wordSpacing();
@@ -557,12 +663,12 @@ void RenderText::trimmedPrefWidths(float leadWidth,
// 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;
- for (int i = 0; i < len; i++) {
- int linelen = 0;
+ for (unsigned i = 0; i < len; i++) {
+ unsigned linelen = 0;
while (i + linelen < len && text[i + linelen] != '\n')
linelen++;
@@ -609,30 +715,49 @@ float RenderText::maxLogicalWidth() const
return m_maxWidth;
}
+LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak lineBreak)
+{
+ switch (lineBreak) {
+ case LineBreakAuto:
+ case LineBreakAfterWhiteSpace:
+ return LineBreakIteratorMode::Default;
+ case LineBreakLoose:
+ return LineBreakIteratorMode::Loose;
+ case LineBreakNormal:
+ return LineBreakIteratorMode::Normal;
+ case LineBreakStrict:
+ return LineBreakIteratorMode::Strict;
+ }
+ ASSERT_NOT_REACHED();
+ return LineBreakIteratorMode::Default;
+}
+
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)
{
- const RenderStyle& style = renderer->style();
- return font.width(RenderBlock::constructTextRun(renderer, font, style.hyphenString().string(), style));
+ const RenderStyle& style = renderer.style();
+ auto textRun = RenderBlock::constructTextRun(style.hyphenString().string(), style);
+ return font.width(textRun);
}
-static float maxWordFragmentWidth(RenderText* renderer, const 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, unsigned minimumPrefixLength, unsigned minimumSuffixLength, unsigned& 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)
+ ASSERT(word.length() >= minimumSuffixLength);
+ unsigned hyphenLocation = word.length() - minimumSuffixLength;
+ while ((hyphenLocation = lastHyphenLocation(word, hyphenLocation, style.locale())) >= std::max(minimumPrefixLength, 1U))
hyphenLocations.append(hyphenLocation);
if (hyphenLocations.isEmpty())
@@ -640,17 +765,18 @@ static float maxWordFragmentWidth(RenderText* renderer, const RenderStyle& style
hyphenLocations.reverse();
+ // FIXME: Breaking the string at these places in the middle of words is completely broken with complex text.
float minimumFragmentWidthToConsider = font.pixelSize() * 5 / 4 + hyphenWidth(renderer, font);
float maxFragmentWidth = 0;
for (size_t k = 0; k < hyphenLocations.size(); ++k) {
int fragmentLength = hyphenLocations[k] - suffixStart;
StringBuilder fragmentWithHyphen;
- fragmentWithHyphen.append(word + suffixStart, fragmentLength);
+ fragmentWithHyphen.append(word.substring(suffixStart, fragmentLength));
fragmentWithHyphen.append(style.hyphenString());
- TextRun run = RenderBlock::constructTextRun(renderer, font, fragmentWithHyphen.deprecatedCharacters(), fragmentWithHyphen.length(), style);
+ TextRun run = RenderBlock::constructTextRun(fragmentWithHyphen.toString(), style);
run.setCharactersLength(fragmentWithHyphen.length());
- run.setCharacterScanForCodePath(!renderer->canUseSimpleFontCodePath());
+ run.setCharacterScanForCodePath(!renderer.canUseSimpleFontCodePath());
float fragmentWidth = font.width(run, &fallbackFonts, &glyphOverflow);
// Narrow prefixes are ignored. See tryHyphenating in RenderBlockLineLayout.cpp.
@@ -664,7 +790,7 @@ static float maxWordFragmentWidth(RenderText* renderer, const RenderStyle& style
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);
@@ -673,7 +799,6 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_endMinWidth = 0;
m_maxWidth = 0;
- float currMinWidth = 0;
float currMaxWidth = 0;
m_hasBreakableChar = false;
m_hasBreak = false;
@@ -682,46 +807,50 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_hasEndWS = false;
const RenderStyle& style = this->style();
- const Font& font = style.font(); // FIXME: This ignores first-line.
+ const FontCascade& font = style.fontCascade(); // FIXME: This ignores first-line.
float wordSpacing = font.wordSpacing();
- int len = textLength();
- LazyLineBreakIterator breakIterator(m_text, style.locale());
+ unsigned len = textLength();
+ auto iteratorMode = mapLineBreakToIteratorMode(style.lineBreak());
+ LazyLineBreakIterator breakIterator(m_text, style.locale(), iteratorMode);
bool needsWordSpacing = false;
bool ignoringSpaces = false;
bool isSpace = false;
bool firstWord = true;
bool firstLine = true;
- int nextBreakable = -1;
- int lastWordBoundary = 0;
+ std::optional<unsigned> nextBreakable;
+ unsigned lastWordBoundary = 0;
// Non-zero only when kerning is enabled, in which case we measure words with their trailing
// space, then subtract its width.
- float wordTrailingSpaceWidth = font.typesettingFeatures() & Kerning ? font.width(RenderBlock::constructTextRun(this, font, &space, 1, style), &fallbackFonts) + wordSpacing : 0;
-
+ WordTrailingSpace wordTrailingSpace(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 = std::numeric_limits<float>::max();
- int minimumPrefixLength = 0;
- int minimumSuffixLength = 0;
+ unsigned minimumPrefixLength = 0;
+ unsigned minimumSuffixLength = 0;
if (style.hyphens() == HyphensAuto && canHyphenate(style.locale())) {
maxWordWidth = 0;
// Map 'hyphenate-limit-{before,after}: auto;' to 2.
- minimumPrefixLength = style.hyphenationLimitBefore();
- if (minimumPrefixLength < 0)
- minimumPrefixLength = 2;
+ auto before = style.hyphenationLimitBefore();
+ minimumPrefixLength = before < 0 ? 2 : before;
- minimumSuffixLength = style.hyphenationLimitAfter();
- if (minimumSuffixLength < 0)
- minimumSuffixLength = 2;
+ auto after = style.hyphenationLimitAfter();
+ minimumSuffixLength = after < 0 ? 2 : after;
}
- int firstGlyphLeftOverflow = -1;
+ std::optional<int> firstGlyphLeftOverflow;
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 canUseLineBreakShortcut = iteratorMode == LineBreakIteratorMode::Default;
- for (int i = 0; i < len; i++) {
+ for (unsigned i = 0; i < len; i++) {
UChar c = uncheckedCharacterAt(i);
bool previousCharacterIsSpace = isSpace;
@@ -748,11 +877,8 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
if ((isSpace || isNewline) && i == len - 1)
m_hasEndWS = true;
- if (!ignoringSpaces && style.collapseWhiteSpace() && previousCharacterIsSpace && isSpace)
- ignoringSpaces = true;
-
- if (ignoringSpaces && !isSpace)
- ignoringSpaces = false;
+ ignoringSpaces |= style.collapseWhiteSpace() && previousCharacterIsSpace && isSpace;
+ ignoringSpaces &= isSpace;
// Ignore spaces and soft hyphens
if (ignoringSpaces) {
@@ -760,22 +886,23 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
lastWordBoundary++;
continue;
} else if (c == softHyphen && style.hyphens() != HyphensNone) {
+ ASSERT(i >= lastWordBoundary);
currMaxWidth += widthFromCache(font, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
- if (firstGlyphLeftOverflow < 0)
+ 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, canUseLineBreakShortcut, keepAllWords);
bool betweenWords = true;
- int j = i;
+ unsigned j = i;
while (c != '\n' && !isSpaceAccordingToStyle(c, style) && c != '\t' && (c != softHyphen || style.hyphens() == HyphensNone)) {
j++;
if (j == len)
break;
c = uncheckedCharacterAt(j);
- if (isBreakable(breakIterator, j, nextBreakable, breakNBSP) && characterAt(j - 1) != softHyphen)
+ if (isBreakable(breakIterator, j, nextBreakable, breakNBSP, canUseLineBreakShortcut, keepAllWords) && characterAt(j - 1) != softHyphen)
break;
if (breakAll) {
betweenWords = false;
@@ -783,26 +910,33 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
}
}
- int wordLen = j - i;
+ unsigned wordLen = j - i;
if (wordLen) {
+ float currMinWidth = 0;
bool isSpace = (j < len) && isSpaceAccordingToStyle(c, style);
float w;
- if (wordTrailingSpaceWidth && isSpace)
- w = widthFromCache(font, i, wordLen + 1, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style) - wordTrailingSpaceWidth;
+ std::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(font, i, wordLen, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
if (c == softHyphen && style.hyphens() != HyphensNone)
- currMinWidth += hyphenWidth(this, font);
+ currMinWidth = hyphenWidth(*this, font);
}
if (w > maxWordWidth) {
- int suffixStart;
- float maxFragmentWidth = maxWordFragmentWidth(this, style, font, deprecatedCharacters() + i, wordLen, minimumPrefixLength, minimumSuffixLength, suffixStart, fallbackFonts, glyphOverflow);
+ unsigned suffixStart;
+ 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(font, i + suffixStart, wordLen - suffixStart + 1, leadWidth + currMaxWidth, 0, 0, style) - wordTrailingSpaceWidth;
+ std::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(font, i + suffixStart, wordLen - suffixStart, leadWidth + currMaxWidth, 0, 0, style);
@@ -814,14 +948,16 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
maxWordWidth = w;
}
- if (firstGlyphLeftOverflow < 0)
+ if (!firstGlyphLeftOverflow)
firstGlyphLeftOverflow = glyphOverflow.left;
currMinWidth += w;
if (betweenWords) {
if (lastWordBoundary == i)
currMaxWidth += w;
- else
+ else {
+ ASSERT(j >= lastWordBoundary);
currMaxWidth += widthFromCache(font, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
+ }
lastWordBoundary = j;
}
@@ -831,7 +967,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
// 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) {
@@ -845,9 +981,7 @@ 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 {
@@ -856,10 +990,6 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
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;
@@ -872,7 +1002,7 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
m_maxWidth = currMaxWidth;
currMaxWidth = 0;
} else {
- TextRun run = RenderBlock::constructTextRun(this, font, this, i, 1, style);
+ TextRun run = RenderBlock::constructTextRun(*this, i, 1, style);
run.setCharactersLength(len - i);
ASSERT(run.charactersLength() >= run.length());
run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
@@ -887,13 +1017,11 @@ void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
}
}
- if (firstGlyphLeftOverflow > 0)
- glyphOverflow.left = firstGlyphLeftOverflow;
+ glyphOverflow.left = firstGlyphLeftOverflow.value_or(glyphOverflow.left);
if ((needsWordSpacing && len > 1) || (ignoringSpaces && !firstWord))
currMaxWidth += wordSpacing;
- m_minWidth = std::max(currMinWidth, m_minWidth);
m_maxWidth = std::max(currMaxWidth, m_maxWidth);
if (!style.autoWrap())
@@ -937,21 +1065,14 @@ 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 firstTextBox() ? firstTextBox()->x() : 0;
+ return m_lineBoxes.firstRunLocation();
}
-float RenderText::firstRunY() const
-{
- return firstTextBox() ? firstTextBox()->y() : 0;
-}
-
void RenderText::setSelectionState(SelectionState state)
{
if (state != SelectionNone)
@@ -981,13 +1102,13 @@ void RenderText::setTextWithOffset(const String& text, unsigned offset, unsigned
setText(text, force || m_linesDirty);
}
-static inline bool isInlineFlowOrEmptyText(const RenderObject* o)
+static inline bool isInlineFlowOrEmptyText(const RenderObject& renderer)
{
- 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();
@@ -998,15 +1119,21 @@ 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;
}
+LayoutUnit RenderText::topOfFirstText() const
+{
+ return firstTextBox()->root().lineTop();
+}
+
void applyTextTransform(const RenderStyle& style, String& text, UChar previousCharacter)
{
switch (style.textTransform()) {
@@ -1016,23 +1143,19 @@ void applyTextTransform(const RenderStyle& style, String& text, UChar previousCh
makeCapitalized(&text, previousCharacter);
break;
case UPPERCASE:
- text = text.upper(style.locale());
+ text = text.convertToUppercaseWithLocale(style.locale());
break;
case LOWERCASE:
- text = text.lower(style.locale());
+ text = text.convertToLowercaseWithLocale(style.locale());
break;
}
}
-void RenderText::setTextInternal(const String& text)
+void RenderText::setRenderedText(const String& text)
{
ASSERT(!text.isNull());
- if (m_originalTextDiffersFromRendered) {
- originalTextMap().remove(this);
- m_originalTextDiffersFromRendered = false;
- }
- String originalText = text;
+ String originalText = this->originalText();
m_text = text;
@@ -1043,30 +1166,28 @@ void RenderText::setTextInternal(const String& text)
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;
+#if !PLATFORM(IOS)
+ // We use the same characters here as for list markers.
+ // See the listMarkerText function in RenderListMarker.cpp.
case TSCIRCLE:
-#if PLATFORM(IOS)
- secureText(blackCircle);
-#else
secureText(whiteBullet);
-#endif
break;
case TSDISC:
-#if PLATFORM(IOS)
- secureText(blackCircle);
-#else
secureText(bullet);
-#endif
break;
case TSSQUARE:
-#if PLATFORM(IOS)
- secureText(blackCircle);
-#else
secureText(blackSquare);
+ break;
+#else
+ // FIXME: Why this quirk on iOS?
+ case TSCIRCLE:
+ case TSDISC:
+ case TSSQUARE:
+ secureText(blackCircle);
+ break;
#endif
}
@@ -1074,33 +1195,70 @@ void RenderText::setTextInternal(const String& text)
m_isAllASCII = m_text.containsOnlyASCII();
m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
-
+ m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
+
if (m_text != originalText) {
- originalTextMap().add(this, 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 (!textLength())
+ // 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 = 0;
+
+ 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];
}
- 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();
+ UChar* characters;
+ m_text = String::createUninitialized(length, characters);
+
+ for (unsigned i = 0; i < length; ++i)
+ characters[i] = maskingCharacter;
+ if (characterToReveal)
+ characters[revealedCharactersOffset] = characterToReveal;
+}
+
+bool RenderText::computeCanUseSimplifiedTextMeasuring() const
+{
+ if (!m_canUseSimpleFontCodePath)
+ return false;
+
+ auto& font = style().fontCascade();
+ if (font.wordSpacing() || font.letterSpacing())
+ return false;
+
+ // Additional check on the font codepath.
+ TextRun run(m_text);
+ run.setCharacterScanForCodePath(false);
+ if (font.codePath(run) != FontCascade::Simple)
+ return false;
+
+ auto whitespaceIsCollapsed = style().collapseWhiteSpace();
+ for (unsigned i = 0; i < m_text.length(); ++i) {
+ if ((!whitespaceIsCollapsed && m_text[i] == '\t') || m_text[i] == noBreakSpace || m_text[i] >= HiraganaLetterSmallA)
+ return false;
}
+ return true;
}
void RenderText::setText(const String& text, bool force)
@@ -1110,12 +1268,19 @@ void RenderText::setText(const String& text, bool force)
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 (parent()->isRenderBlockFlow())
- toRenderBlockFlow(parent())->invalidateLineLayoutPath();
+ if (is<RenderBlockFlow>(*parent()))
+ downcast<RenderBlockFlow>(*parent()).invalidateLineLayoutPath();
if (AXObjectCache* cache = document().existingAXObjectCache())
cache->textChanged(this);
@@ -1147,33 +1312,26 @@ std::unique_ptr<InlineTextBox> RenderText::createTextBox()
void RenderText::positionLineBox(InlineTextBox& textBox)
{
- // FIXME: should not be needed!!!
- if (!textBox.len()) {
- // We want the box to be destroyed.
- textBox.removeFromParent();
- m_lineBoxes.remove(textBox);
- delete &textBox;
+ if (!textBox.len())
return;
- }
-
m_containsReversedText |= !textBox.isLeftToRightDirection();
}
void RenderText::ensureLineBoxes()
{
- if (!parent()->isRenderBlockFlow())
+ if (!is<RenderBlockFlow>(*parent()))
return;
- toRenderBlockFlow(parent())->ensureLineBoxes();
+ downcast<RenderBlockFlow>(*parent()).ensureLineBoxes();
}
const SimpleLineLayout::Layout* RenderText::simpleLineLayout() const
{
- if (!parent()->isRenderBlockFlow())
+ if (!is<RenderBlockFlow>(*parent()))
return nullptr;
- return toRenderBlockFlow(parent())->simpleLineLayout();
+ 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;
@@ -1182,10 +1340,10 @@ float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine,
len = textLength() - from;
const RenderStyle& lineStyle = firstLine ? firstLineStyle() : style();
- return width(from, len, lineStyle.font(), xPos, fallbackFonts, glyphOverflow);
+ 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())
@@ -1193,7 +1351,7 @@ float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
const RenderStyle& style = this->style();
float w;
- if (&f == &style.font()) {
+ if (&f == &style.fontCascade()) {
if (!style.preserveNewline() && !from && len == textLength() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
if (fallbackFonts) {
ASSERT(glyphOverflow);
@@ -1208,7 +1366,7 @@ float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
} else
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(*this, from, len, style);
run.setCharactersLength(textLength() - from);
ASSERT(run.charactersLength() >= run.length());
@@ -1223,8 +1381,8 @@ float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
IntRect RenderText::linesBoundingBox() const
{
- if (auto layout = simpleLineLayout())
- return SimpleLineLayout::computeTextBoundingBox(*this, *layout);
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::computeBoundingBox(*this, *layout);
return m_lineBoxes.boundingBox(*this);
}
@@ -1251,20 +1409,19 @@ 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();
- RenderBlock* cb = containingBlock();
- if (!cb)
+ if (!containingBlock())
return LayoutRect();
// Now calculate startPos and endPos for painting selection.
// We include a selection while endPos > 0
- int startPos, endPos;
+ unsigned startPos, endPos;
if (selectionState() == SelectionInside) {
// We are fully selected.
startPos = 0;
@@ -1280,31 +1437,43 @@ LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* rep
if (startPos == endPos)
return IntRect();
- LayoutRect rect = m_lineBoxes.selectionRectForRange(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
{
- if (auto layout = simpleLineLayout())
- return SimpleLineLayout::findTextCaretMinimumOffset(*this, *layout);
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::findCaretMinimumOffset(*this, *layout);
return m_lineBoxes.caretMinOffset();
}
int RenderText::caretMaxOffset() const
{
- if (auto layout = simpleLineLayout())
- return SimpleLineLayout::findTextCaretMaximumOffset(*this, *layout);
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::findCaretMaximumOffset(*this, *layout);
return m_lineBoxes.caretMaxOffset(*this);
}
@@ -1322,14 +1491,14 @@ bool RenderText::containsRenderedCharacterOffset(unsigned offset) const
bool RenderText::containsCaretOffset(unsigned offset) const
{
- if (auto layout = simpleLineLayout())
- return SimpleLineLayout::containsTextCaretOffset(*this, *layout, offset);
+ if (auto* layout = simpleLineLayout())
+ return SimpleLineLayout::containsCaretOffset(*this, *layout, offset);
return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CaretOffset);
}
bool RenderText::hasRenderedText() const
{
- if (auto layout = simpleLineLayout())
+ if (auto* layout = simpleLineLayout())
return SimpleLineLayout::isTextRendered(*this, *layout);
return m_lineBoxes.hasRenderedText();
}
@@ -1340,72 +1509,93 @@ int RenderText::previousOffset(int current) const
return current - 1;
StringImpl* textImpl = m_text.impl();
- TextBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
+ UBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
if (!iterator)
return current - 1;
- long result = textBreakPreceding(iterator, current);
- if (result == TextBreakDone)
+ long result = ubrk_preceding(iterator, current);
+ if (result == UBRK_DONE)
result = current - 1;
return result;
}
-#if PLATFORM(MAC) || PLATFORM(EFL)
-
-#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(GTK)
+
+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;
-inline bool isHangulLVT(UChar32 character)
+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) || PLATFORM(EFL)
- 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 UBreakIterator.
+ // 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(GTK)
bool sawRegionalIndicator = false;
+ bool sawEmojiGroupCandidate = false;
+ bool sawEmojiFitzpatrickModifier = 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 (sawEmojiFitzpatrickModifier) {
+ if (isEmojiFitzpatrickModifier(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
@@ -1418,15 +1608,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 (isEmojiFitzpatrickModifier(character)) {
+ sawEmojiFitzpatrickModifier = true;
+ continue;
+ }
+
+ if (isEmojiGroupCandidate(character)) {
+ sawEmojiGroupCandidate = true;
+ continue;
+ }
- if (!isMark(character) && (character != 0xFF9E) && (character != 0xFF9F))
+ // FIXME: Why are FF9E and FF9F special cased here?
+ if (!isMark(character) && character != 0xFF9E && character != 0xFF9F)
break;
}
@@ -1434,55 +1635,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;
- if (character < HANGUL_JUNGSEONG_START)
- state = HangulStateL;
- else if (character < HANGUL_JONGSEONG_START)
- state = HangulStateV;
- else if (character < HANGUL_SYLLABLE_START)
- state = HangulStateT;
+ if (character < hangulJungseongStart)
+ state = HangulState::L;
+ else if (character < hangulJongseongStart)
+ state = HangulState::V;
+ else if (character < hangulSyllableStart)
+ state = HangulState::T;
else
- state = isHangulLVT(character) ? HangulStateLVT : HangulStateLV;
+ 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
}
@@ -1493,12 +1689,12 @@ int RenderText::nextOffset(int current) const
return current + 1;
StringImpl* textImpl = m_text.impl();
- TextBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
+ UBreakIterator* iterator = cursorMovementIterator(StringView(textImpl->characters16(), textImpl->length()));
if (!iterator)
return current + 1;
- long result = textBreakFollowing(iterator, current);
- if (result == TextBreakDone)
+ long result = ubrk_following(iterator, current);
+ if (result == UBRK_DONE)
result = current + 1;
return result;
@@ -1508,20 +1704,28 @@ bool RenderText::computeCanUseSimpleFontCodePath() const
{
if (isAllASCII() || m_text.is8Bit())
return true;
- return Font::characterRangeCodePath(characters16(), length()) == Font::Simple;
+ return FontCascade::characterRangeCodePath(characters16(), length()) == FontCascade::Simple;
}
-void RenderText::momentarilyRevealLastTypedCharacter(unsigned lastTypedCharacterOffset)
+void RenderText::momentarilyRevealLastTypedCharacter(unsigned offsetAfterLastTypedCharacter)
{
- if (!gSecureTextTimers)
- gSecureTextTimers = new SecureTextTimerMap;
+ if (style().textSecurity() == TSNONE)
+ return;
+ auto& secureTextTimer = secureTextTimers().add(this, nullptr).iterator->value;
+ if (!secureTextTimer)
+ secureTextTimer = std::make_unique<SecureTextTimer>(*this);
+ secureTextTimer->restart(offsetAfterLastTypedCharacter);
+}
- SecureTextTimer* secureTextTimer = gSecureTextTimers->get(this);
- if (!secureTextTimer) {
- secureTextTimer = new SecureTextTimer(this);
- gSecureTextTimers->add(this, secureTextTimer);
- }
- secureTextTimer->restartWithNewText(lastTypedCharacterOffset);
+StringView RenderText::stringView(unsigned start, std::optional<unsigned> stop) const
+{
+ unsigned destination = stop.value_or(textLength());
+ ASSERT(start <= length());
+ ASSERT(destination <= length());
+ ASSERT(start <= destination);
+ if (is8Bit())
+ return StringView(characters8() + start, destination - start);
+ return StringView(characters16() + start, destination - start);
}
} // namespace WebCore