diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/rendering/RenderText.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/rendering/RenderText.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderText.cpp | 888 |
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 |