1/*
2 * (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 2000 Dirk Mueller (mueller@kde.org)
4 * Copyright (C) 2004-2019 Apple Inc. All rights reserved.
5 * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
6 * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com)
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25#include "config.h"
26#include "RenderText.h"
27
28#include "AXObjectCache.h"
29#include "BreakLines.h"
30#include "BreakingContext.h"
31#include "CharacterProperties.h"
32#include "DocumentMarkerController.h"
33#include "EllipsisBox.h"
34#include "FloatQuad.h"
35#include "Frame.h"
36#include "FrameView.h"
37#include "HTMLParserIdioms.h"
38#include "Hyphenation.h"
39#include "InlineTextBox.h"
40#include "Range.h"
41#include "RenderBlock.h"
42#include "RenderCombineText.h"
43#include "RenderInline.h"
44#include "RenderLayer.h"
45#include "RenderView.h"
46#include "RenderedDocumentMarker.h"
47#include "Settings.h"
48#include "SimpleLineLayoutFunctions.h"
49#include "Text.h"
50#include "TextResourceDecoder.h"
51#include "VisiblePosition.h"
52#include <wtf/IsoMallocInlines.h>
53#include <wtf/NeverDestroyed.h>
54#include <wtf/text/StringBuilder.h>
55#include <wtf/text/TextBreakIterator.h>
56#include <wtf/unicode/CharacterNames.h>
57
58#if PLATFORM(IOS_FAMILY)
59#include "Document.h"
60#include "EditorClient.h"
61#include "LogicalSelectionOffsetCaches.h"
62#include "Page.h"
63#include "SelectionRect.h"
64#endif
65
66namespace WebCore {
67
68using namespace WTF::Unicode;
69
70WTF_MAKE_ISO_ALLOCATED_IMPL(RenderText);
71
72struct SameSizeAsRenderText : public RenderObject {
73 void* pointers[2];
74 uint32_t bitfields : 16;
75#if ENABLE(TEXT_AUTOSIZING)
76 float candidateTextSize;
77#endif
78 float widths[4];
79 String text;
80};
81
82COMPILE_ASSERT(sizeof(RenderText) == sizeof(SameSizeAsRenderText), RenderText_should_stay_small);
83
84class SecureTextTimer final : private TimerBase {
85 WTF_MAKE_FAST_ALLOCATED;
86public:
87 explicit SecureTextTimer(RenderText&);
88 void restart(unsigned offsetAfterLastTypedCharacter);
89
90 unsigned takeOffsetAfterLastTypedCharacter();
91
92private:
93 void fired() override;
94 RenderText& m_renderer;
95 unsigned m_offsetAfterLastTypedCharacter { 0 };
96};
97
98typedef HashMap<RenderText*, std::unique_ptr<SecureTextTimer>> SecureTextTimerMap;
99
100static SecureTextTimerMap& secureTextTimers()
101{
102 static NeverDestroyed<SecureTextTimerMap> map;
103 return map.get();
104}
105
106inline SecureTextTimer::SecureTextTimer(RenderText& renderer)
107 : m_renderer(renderer)
108{
109}
110
111inline void SecureTextTimer::restart(unsigned offsetAfterLastTypedCharacter)
112{
113 m_offsetAfterLastTypedCharacter = offsetAfterLastTypedCharacter;
114 startOneShot(1_s * m_renderer.settings().passwordEchoDurationInSeconds());
115}
116
117inline unsigned SecureTextTimer::takeOffsetAfterLastTypedCharacter()
118{
119 unsigned offset = m_offsetAfterLastTypedCharacter;
120 m_offsetAfterLastTypedCharacter = 0;
121 return offset;
122}
123
124void SecureTextTimer::fired()
125{
126 ASSERT(secureTextTimers().get(&m_renderer) == this);
127 m_offsetAfterLastTypedCharacter = 0;
128 m_renderer.setText(m_renderer.text(), true /* forcing setting text as it may be masked later */);
129}
130
131static HashMap<const RenderText*, String>& originalTextMap()
132{
133 static NeverDestroyed<HashMap<const RenderText*, String>> map;
134 return map;
135}
136
137static HashMap<const RenderText*, WeakPtr<RenderInline>>& inlineWrapperForDisplayContentsMap()
138{
139 static NeverDestroyed<HashMap<const RenderText*, WeakPtr<RenderInline>>> map;
140 return map;
141}
142
143static inline UChar convertNoBreakSpace(UChar character)
144{
145 return character == noBreakSpace ? ' ' : character;
146}
147
148String capitalize(const String& string, UChar previousCharacter)
149{
150 // FIXME: Change this to use u_strToTitle instead of u_totitle and to consider locale.
151
152 unsigned length = string.length();
153
154 // Prepend the previous character, and convert NO BREAK SPACE to SPACE so ICU will see a word separator.
155 Vector<UChar> wordBreakCharacters;
156 wordBreakCharacters.grow(length + 1);
157 wordBreakCharacters[0] = convertNoBreakSpace(previousCharacter);
158 for (unsigned i = 1; i < length + 1; i++)
159 wordBreakCharacters[i] = convertNoBreakSpace(string[i - 1]);
160
161 auto* boundary = wordBreakIterator(StringView { wordBreakCharacters.data(), length + 1 });
162 if (!boundary)
163 return string;
164
165 StringBuilder result;
166 result.reserveCapacity(length);
167
168 int32_t endOfWord;
169 int32_t startOfWord = ubrk_first(boundary);
170 for (endOfWord = ubrk_next(boundary); endOfWord != UBRK_DONE; startOfWord = endOfWord, endOfWord = ubrk_next(boundary)) {
171 if (startOfWord) // Ignore first char of previous string
172 result.append(string[startOfWord - 1] == noBreakSpace ? noBreakSpace : u_totitle(wordBreakCharacters[startOfWord]));
173 for (int i = startOfWord + 1; i < endOfWord; i++)
174 result.append(string[i - 1]);
175 }
176
177 return result == string ? string : result.toString();
178}
179
180inline RenderText::RenderText(Node& node, const String& text)
181 : RenderObject(node)
182 , m_hasTab(false)
183 , m_linesDirty(false)
184 , m_containsReversedText(false)
185 , m_isAllASCII(text.impl()->isAllASCII())
186 , m_knownToHaveNoOverflowAndNoFallbackFonts(false)
187 , m_useBackslashAsYenSymbol(false)
188 , m_originalTextDiffersFromRendered(false)
189 , m_hasInlineWrapperForDisplayContents(false)
190 , m_text(text)
191{
192 ASSERT(!m_text.isNull());
193 setIsText();
194 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
195
196 // FIXME: Find out how to increment the visually non empty character count when the font becomes available.
197 auto isTextVisible = false;
198 if (auto* parentElement = node.parentElement()) {
199 auto* style = parentElement->renderer() ? &parentElement->renderer()->style() : nullptr;
200 isTextVisible = style && style->visibility() == Visibility::Visible && !style->fontCascade().isLoadingCustomFonts();
201 }
202
203 if (isTextVisible)
204 view().frameView().incrementVisuallyNonEmptyCharacterCount(text);
205}
206
207RenderText::RenderText(Text& textNode, const String& text)
208 : RenderText(static_cast<Node&>(textNode), text)
209{
210}
211
212RenderText::RenderText(Document& document, const String& text)
213 : RenderText(static_cast<Node&>(document), text)
214{
215}
216
217RenderText::~RenderText()
218{
219 // Do not add any code here. Add it to willBeDestroyed() instead.
220 ASSERT(!originalTextMap().contains(this));
221}
222
223const char* RenderText::renderName() const
224{
225 return "RenderText";
226}
227
228Text* RenderText::textNode() const
229{
230 return downcast<Text>(RenderObject::node());
231}
232
233bool RenderText::isTextFragment() const
234{
235 return false;
236}
237
238bool RenderText::computeUseBackslashAsYenSymbol() const
239{
240 const RenderStyle& style = this->style();
241 const auto& fontDescription = style.fontDescription();
242 if (style.fontCascade().useBackslashAsYenSymbol())
243 return true;
244 if (fontDescription.isSpecifiedFont())
245 return false;
246 const TextEncoding* encoding = document().decoder() ? &document().decoder()->encoding() : 0;
247 if (encoding && encoding->backslashAsCurrencySymbol() != '\\')
248 return true;
249 return false;
250}
251
252void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
253{
254 // There is no need to ever schedule repaints from a style change of a text run, since
255 // we already did this for the parent of the text run.
256 // We do have to schedule layouts, though, since a style change can force us to
257 // need to relayout.
258 if (diff == StyleDifference::Layout) {
259 setNeedsLayoutAndPrefWidthsRecalc();
260 m_knownToHaveNoOverflowAndNoFallbackFonts = false;
261 }
262
263 const RenderStyle& newStyle = style();
264 bool needsResetText = false;
265 if (!oldStyle) {
266 m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
267 needsResetText = m_useBackslashAsYenSymbol;
268 } else if (oldStyle->fontCascade().useBackslashAsYenSymbol() != newStyle.fontCascade().useBackslashAsYenSymbol()) {
269 m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
270 needsResetText = true;
271 }
272
273 if (!oldStyle || oldStyle->fontCascade() != newStyle.fontCascade())
274 m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
275
276 TextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TextTransform::None;
277 TextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TextSecurity::None;
278 if (needsResetText || oldTransform != newStyle.textTransform() || oldSecurity != newStyle.textSecurity())
279 RenderText::setText(originalText(), true);
280}
281
282void RenderText::removeAndDestroyTextBoxes()
283{
284 if (!renderTreeBeingDestroyed())
285 m_lineBoxes.removeAllFromParent(*this);
286#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
287 else
288 m_lineBoxes.invalidateParentChildLists();
289#endif
290 m_lineBoxes.deleteAll();
291}
292
293void RenderText::willBeDestroyed()
294{
295 secureTextTimers().remove(this);
296
297 removeAndDestroyTextBoxes();
298
299 if (m_originalTextDiffersFromRendered)
300 originalTextMap().remove(this);
301
302 setInlineWrapperForDisplayContents(nullptr);
303
304 RenderObject::willBeDestroyed();
305}
306
307void RenderText::deleteLineBoxesBeforeSimpleLineLayout()
308{
309 m_lineBoxes.deleteAll();
310}
311
312String RenderText::originalText() const
313{
314 return m_originalTextDiffersFromRendered ? originalTextMap().get(this) : m_text;
315}
316
317void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
318{
319 if (auto* layout = simpleLineLayout()) {
320 rects.appendVector(SimpleLineLayout::collectAbsoluteRects(*this, *layout, accumulatedOffset));
321 return;
322 }
323 rects.appendVector(m_lineBoxes.absoluteRects(accumulatedOffset));
324}
325
326Vector<IntRect> RenderText::absoluteRectsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
327{
328 const_cast<RenderText&>(*this).ensureLineBoxes();
329
330 // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
331 // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
332 // function to take ints causes various internal mismatches. But selectionRect takes ints, and
333 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
334 // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
335 ASSERT(end == UINT_MAX || end <= INT_MAX);
336 ASSERT(start <= INT_MAX);
337 start = std::min(start, static_cast<unsigned>(INT_MAX));
338 end = std::min(end, static_cast<unsigned>(INT_MAX));
339
340 return m_lineBoxes.absoluteRectsForRange(*this, start, end, useSelectionHeight, wasFixed);
341}
342
343#if PLATFORM(IOS_FAMILY)
344// This function is similar in spirit to addLineBoxRects, but returns rectangles
345// which are annotated with additional state which helps the iPhone draw selections in its unique way.
346// Full annotations are added in this class.
347void RenderText::collectSelectionRects(Vector<SelectionRect>& rects, unsigned start, unsigned end)
348{
349 // FIXME: Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
350 // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
351 // function to take ints causes various internal mismatches. But selectionRect takes ints, and
352 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
353 // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
354 ASSERT(end == std::numeric_limits<unsigned>::max() || end <= std::numeric_limits<int>::max());
355 ASSERT(start <= std::numeric_limits<int>::max());
356 start = std::min(start, static_cast<unsigned>(std::numeric_limits<int>::max()));
357 end = std::min(end, static_cast<unsigned>(std::numeric_limits<int>::max()));
358
359 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) {
360 LayoutRect rect;
361 // Note, box->end() returns the index of the last character, not the index past it.
362 if (start <= box->start() && box->end() < end)
363 rect = box->localSelectionRect(start, end);
364 else {
365 unsigned realEnd = std::min(box->end() + 1, end);
366 rect = box->localSelectionRect(start, realEnd);
367 if (rect.isEmpty())
368 continue;
369 }
370
371 if (box->root().isFirstAfterPageBreak()) {
372 if (box->isHorizontal())
373 rect.shiftYEdgeTo(box->root().lineTopWithLeading());
374 else
375 rect.shiftXEdgeTo(box->root().lineTopWithLeading());
376 }
377
378 RenderBlock* containingBlock = this->containingBlock();
379 // Map rect, extended left to leftOffset, and right to rightOffset, through transforms to get minX and maxX.
380 LogicalSelectionOffsetCaches cache(*containingBlock);
381 LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, box->logicalTop(), cache);
382 LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, box->logicalTop(), cache);
383 LayoutRect extentsRect = rect;
384 if (box->isHorizontal()) {
385 extentsRect.setX(leftOffset);
386 extentsRect.setWidth(rightOffset - leftOffset);
387 } else {
388 extentsRect.setY(leftOffset);
389 extentsRect.setHeight(rightOffset - leftOffset);
390 }
391 extentsRect = localToAbsoluteQuad(FloatRect(extentsRect)).enclosingBoundingBox();
392 if (!box->isHorizontal())
393 extentsRect = extentsRect.transposedRect();
394 bool isFirstOnLine = !box->previousOnLineExists();
395 bool isLastOnLine = !box->nextOnLineExists();
396 if (containingBlock->isRubyBase() || containingBlock->isRubyText())
397 isLastOnLine = !containingBlock->containingBlock()->inlineBoxWrapper()->nextOnLineExists();
398
399 bool containsStart = box->start() <= start && box->end() + 1 >= start;
400 bool containsEnd = box->start() <= end && box->end() + 1 >= end;
401
402 bool isFixed = false;
403 IntRect absRect = localToAbsoluteQuad(FloatRect(rect), UseTransforms, &isFixed).enclosingBoundingBox();
404 bool boxIsHorizontal = !box->isSVGInlineTextBox() ? box->isHorizontal() : !style().isVerticalWritingMode();
405 // If the containing block is an inline element, we want to check the inlineBoxWrapper orientation
406 // to determine the orientation of the block. In this case we also use the inlineBoxWrapper to
407 // determine if the element is the last on the line.
408 if (containingBlock->inlineBoxWrapper()) {
409 if (containingBlock->inlineBoxWrapper()->isHorizontal() != boxIsHorizontal) {
410 boxIsHorizontal = containingBlock->inlineBoxWrapper()->isHorizontal();
411 isLastOnLine = !containingBlock->inlineBoxWrapper()->nextOnLineExists();
412 }
413 }
414
415 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())));
416 }
417}
418#endif
419
420Vector<FloatQuad> RenderText::absoluteQuadsClippedToEllipsis() const
421{
422 if (auto* layout = simpleLineLayout()) {
423 ASSERT(style().textOverflow() != TextOverflow::Ellipsis);
424 return SimpleLineLayout::collectAbsoluteQuads(*this, *layout, nullptr);
425 }
426 return m_lineBoxes.absoluteQuads(*this, nullptr, RenderTextLineBoxes::ClipToEllipsis);
427}
428
429void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
430{
431 if (auto* layout = simpleLineLayout()) {
432 quads.appendVector(SimpleLineLayout::collectAbsoluteQuads(*this, *layout, wasFixed));
433 return;
434 }
435 quads.appendVector(m_lineBoxes.absoluteQuads(*this, wasFixed, RenderTextLineBoxes::NoClipping));
436}
437
438Vector<FloatQuad> RenderText::absoluteQuadsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
439{
440 // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
441 // to mean "all the way to the end". InlineTextBox coordinates are unsigneds, so changing this
442 // function to take ints causes various internal mismatches. But selectionRect takes ints, and
443 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
444 // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
445 ASSERT(end == UINT_MAX || end <= INT_MAX);
446 ASSERT(start <= INT_MAX);
447 start = std::min(start, static_cast<unsigned>(INT_MAX));
448 end = std::min(end, static_cast<unsigned>(INT_MAX));
449 if (simpleLineLayout() && !useSelectionHeight)
450 return collectAbsoluteQuadsForRange(*this, start, end, *simpleLineLayout(), wasFixed);
451 const_cast<RenderText&>(*this).ensureLineBoxes();
452 return m_lineBoxes.absoluteQuadsForRange(*this, start, end, useSelectionHeight, wasFixed);
453}
454
455Position RenderText::positionForPoint(const LayoutPoint& point)
456{
457 if (simpleLineLayout() && parent()->firstChild() == parent()->lastChild()) {
458 auto offset = SimpleLineLayout::textOffsetForPoint(point, *this, *simpleLineLayout());
459 // Did not find a valid offset. Fall back to the normal line layout based Position.
460 if (offset == text().length())
461 return positionForPoint(point, nullptr).deepEquivalent();
462 auto position = Position(textNode(), offset);
463 ASSERT(position == positionForPoint(point, nullptr).deepEquivalent());
464 return position;
465 }
466 return positionForPoint(point, nullptr).deepEquivalent();
467}
468
469VisiblePosition RenderText::positionForPoint(const LayoutPoint& point, const RenderFragmentContainer*)
470{
471 ensureLineBoxes();
472 return m_lineBoxes.positionForPoint(*this, point);
473}
474
475LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, unsigned caretOffset, LayoutUnit* extraWidthToEndOfLine)
476{
477 if (!inlineBox)
478 return LayoutRect();
479
480 auto& box = downcast<InlineTextBox>(*inlineBox);
481 float left = box.positionForOffset(caretOffset);
482 return box.root().computeCaretRect(left, caretWidth, extraWidthToEndOfLine);
483}
484
485ALWAYS_INLINE float RenderText::widthFromCache(const FontCascade& f, unsigned start, unsigned len, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow, const RenderStyle& style) const
486{
487 if (style.hasTextCombine() && is<RenderCombineText>(*this)) {
488 const RenderCombineText& combineText = downcast<RenderCombineText>(*this);
489 if (combineText.isCombined())
490 return combineText.combinedTextWidth(f);
491 }
492
493 if (f.isFixedPitch() && f.fontDescription().variantSettings().isAllNormal() && m_isAllASCII && (!glyphOverflow || !glyphOverflow->computeBounds)) {
494 float monospaceCharacterWidth = f.spaceWidth();
495 float w = 0;
496 bool isSpace;
497 for (unsigned i = start; i < start + len; i++) {
498 char c = text()[i];
499 if (c <= ' ') {
500 if (c == ' ' || c == '\n') {
501 w += monospaceCharacterWidth;
502 isSpace = true;
503 } else if (c == '\t') {
504 if (style.collapseWhiteSpace()) {
505 w += monospaceCharacterWidth;
506 isSpace = true;
507 } else {
508 w += f.tabWidth(style.tabSize(), xPos + w);
509 isSpace = false;
510 }
511 } else
512 isSpace = false;
513 } else {
514 w += monospaceCharacterWidth;
515 isSpace = false;
516 }
517 if (isSpace && i > start)
518 w += f.wordSpacing();
519 }
520 return w;
521 }
522
523 TextRun run = RenderBlock::constructTextRun(*this, start, len, style);
524 run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
525 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
526 run.setXPos(xPos);
527 return f.width(run, fallbackFonts, glyphOverflow);
528}
529
530inline bool isHangablePunctuationAtLineStart(UChar c)
531{
532 return U_GET_GC_MASK(c) & (U_GC_PS_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
533}
534
535inline bool isHangablePunctuationAtLineEnd(UChar c)
536{
537 return U_GET_GC_MASK(c) & (U_GC_PE_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
538}
539
540float RenderText::hangablePunctuationStartWidth(unsigned index) const
541{
542 unsigned length = text().length();
543 if (index >= length)
544 return 0;
545
546 if (!isHangablePunctuationAtLineStart(text()[index]))
547 return 0;
548
549 auto& style = this->style();
550 return widthFromCache(style.fontCascade(), index, 1, 0, 0, 0, style);
551}
552
553float RenderText::hangablePunctuationEndWidth(unsigned index) const
554{
555 unsigned length = text().length();
556 if (index >= length)
557 return 0;
558
559 if (!isHangablePunctuationAtLineEnd(text()[index]))
560 return 0;
561
562 auto& style = this->style();
563 return widthFromCache(style.fontCascade(), index, 1, 0, 0, 0, style);
564}
565
566bool RenderText::isHangableStopOrComma(UChar c)
567{
568 return c == 0x002C || c == 0x002E || c == 0x060C || c == 0x06D4 || c == 0x3001
569 || c == 0x3002 || c == 0xFF0C || c == 0xFF0E || c == 0xFE50 || c == 0xFE51
570 || c == 0xFE52 || c == 0xFF61 || c == 0xFF64;
571}
572
573unsigned RenderText::firstCharacterIndexStrippingSpaces() const
574{
575 if (!style().collapseWhiteSpace())
576 return 0;
577
578 unsigned i = 0;
579 for (unsigned length = text().length() ; i < length; ++i) {
580 if (text()[i] != ' ' && (text()[i] != '\n' || style().preserveNewline()) && text()[i] != '\t')
581 break;
582 }
583 return i;
584}
585
586unsigned RenderText::lastCharacterIndexStrippingSpaces() const
587{
588 if (!text().length())
589 return 0;
590
591 if (!style().collapseWhiteSpace())
592 return text().length() - 1;
593
594 int i = text().length() - 1;
595 for ( ; i >= 0; --i) {
596 if (text()[i] != ' ' && (text()[i] != '\n' || style().preserveNewline()) && text()[i] != '\t')
597 break;
598 }
599 return i;
600}
601
602RenderText::Widths RenderText::trimmedPreferredWidths(float leadWidth, bool& stripFrontSpaces)
603{
604 auto& style = this->style();
605 bool collapseWhiteSpace = style.collapseWhiteSpace();
606
607 if (!collapseWhiteSpace)
608 stripFrontSpaces = false;
609
610 if (m_hasTab || preferredLogicalWidthsDirty())
611 computePreferredLogicalWidths(leadWidth);
612
613 Widths widths;
614
615 widths.beginWS = !stripFrontSpaces && m_hasBeginWS;
616 widths.endWS = m_hasEndWS;
617
618 unsigned length = this->length();
619
620 if (!length || (stripFrontSpaces && text().isAllSpecialCharacters<isHTMLSpace>()))
621 return widths;
622
623 widths.min = m_minWidth;
624 widths.max = m_maxWidth;
625
626 widths.beginMin = m_beginMinWidth;
627 widths.endMin = m_endMinWidth;
628
629 widths.hasBreakableChar = m_hasBreakableChar;
630 widths.hasBreak = m_hasBreak;
631
632 if (text()[0] == ' ' || (text()[0] == '\n' && !style.preserveNewline()) || text()[0] == '\t') {
633 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
634 if (stripFrontSpaces)
635 widths.max -= font.width(RenderBlock::constructTextRun(&space, 1, style));
636 else
637 widths.max += font.wordSpacing();
638 }
639
640 stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
641
642 if (!style.autoWrap() || widths.min > widths.max)
643 widths.min = widths.max;
644
645 // Compute our max widths by scanning the string for newlines.
646 if (widths.hasBreak) {
647 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
648 bool firstLine = true;
649 widths.beginMax = widths.max;
650 widths.endMax = widths.max;
651 for (unsigned i = 0; i < length; i++) {
652 unsigned lineLength = 0;
653 while (i + lineLength < length && text()[i + lineLength] != '\n')
654 lineLength++;
655
656 if (lineLength) {
657 widths.endMax = widthFromCache(font, i, lineLength, leadWidth + widths.endMax, 0, 0, style);
658 if (firstLine) {
659 firstLine = false;
660 leadWidth = 0;
661 widths.beginMax = widths.endMax;
662 }
663 i += lineLength;
664 } else if (firstLine) {
665 widths.beginMax = 0;
666 firstLine = false;
667 leadWidth = 0;
668 }
669
670 if (i == length - 1) {
671 // A <pre> run that ends with a newline, as in, e.g.,
672 // <pre>Some text\n\n<span>More text</pre>
673 widths.endMax = 0;
674 }
675 }
676 }
677
678 return widths;
679}
680
681static inline bool isSpaceAccordingToStyle(UChar c, const RenderStyle& style)
682{
683 return c == ' ' || (c == noBreakSpace && style.nbspMode() == NBSPMode::Space);
684}
685
686float RenderText::minLogicalWidth() const
687{
688 if (preferredLogicalWidthsDirty())
689 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
690
691 return m_minWidth;
692}
693
694float RenderText::maxLogicalWidth() const
695{
696 if (preferredLogicalWidthsDirty())
697 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
698
699 return m_maxWidth;
700}
701
702LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak lineBreak)
703{
704 switch (lineBreak) {
705 case LineBreak::Auto:
706 case LineBreak::AfterWhiteSpace:
707 case LineBreak::Anywhere:
708 return LineBreakIteratorMode::Default;
709 case LineBreak::Loose:
710 return LineBreakIteratorMode::Loose;
711 case LineBreak::Normal:
712 return LineBreakIteratorMode::Normal;
713 case LineBreak::Strict:
714 return LineBreakIteratorMode::Strict;
715 }
716 ASSERT_NOT_REACHED();
717 return LineBreakIteratorMode::Default;
718}
719
720void RenderText::computePreferredLogicalWidths(float leadWidth)
721{
722 HashSet<const Font*> fallbackFonts;
723 GlyphOverflow glyphOverflow;
724 computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow);
725 if (fallbackFonts.isEmpty() && !glyphOverflow.left && !glyphOverflow.right && !glyphOverflow.top && !glyphOverflow.bottom)
726 m_knownToHaveNoOverflowAndNoFallbackFonts = true;
727}
728
729static inline float hyphenWidth(RenderText& renderer, const FontCascade& font)
730{
731 const RenderStyle& style = renderer.style();
732 auto textRun = RenderBlock::constructTextRun(style.hyphenString().string(), style);
733 return font.width(textRun);
734}
735
736static float maxWordFragmentWidth(RenderText& renderer, const RenderStyle& style, const FontCascade& font, StringView word, unsigned minimumPrefixLength, unsigned minimumSuffixLength, unsigned& suffixStart, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
737{
738 suffixStart = 0;
739 if (word.length() <= minimumSuffixLength)
740 return 0;
741
742 Vector<int, 8> hyphenLocations;
743 ASSERT(word.length() >= minimumSuffixLength);
744 unsigned hyphenLocation = word.length() - minimumSuffixLength;
745 while ((hyphenLocation = lastHyphenLocation(word, hyphenLocation, style.locale())) >= std::max(minimumPrefixLength, 1U))
746 hyphenLocations.append(hyphenLocation);
747
748 if (hyphenLocations.isEmpty())
749 return 0;
750
751 hyphenLocations.reverse();
752
753 // FIXME: Breaking the string at these places in the middle of words is completely broken with complex text.
754 float minimumFragmentWidthToConsider = font.pixelSize() * 5 / 4 + hyphenWidth(renderer, font);
755 float maxFragmentWidth = 0;
756 for (size_t k = 0; k < hyphenLocations.size(); ++k) {
757 int fragmentLength = hyphenLocations[k] - suffixStart;
758 StringBuilder fragmentWithHyphen;
759 fragmentWithHyphen.append(word.substring(suffixStart, fragmentLength));
760 fragmentWithHyphen.append(style.hyphenString());
761
762 TextRun run = RenderBlock::constructTextRun(fragmentWithHyphen.toString(), style);
763 run.setCharacterScanForCodePath(!renderer.canUseSimpleFontCodePath());
764 float fragmentWidth = font.width(run, &fallbackFonts, &glyphOverflow);
765
766 // Narrow prefixes are ignored. See tryHyphenating in RenderBlockLineLayout.cpp.
767 if (fragmentWidth <= minimumFragmentWidthToConsider)
768 continue;
769
770 suffixStart += fragmentLength;
771 maxFragmentWidth = std::max(maxFragmentWidth, fragmentWidth);
772 }
773
774 return maxFragmentWidth;
775}
776
777void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
778{
779 ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts);
780
781 m_minWidth = 0;
782 m_beginMinWidth = 0;
783 m_endMinWidth = 0;
784 m_maxWidth = 0;
785
786 float currMaxWidth = 0;
787 m_hasBreakableChar = false;
788 m_hasBreak = false;
789 m_hasTab = false;
790 m_hasBeginWS = false;
791 m_hasEndWS = false;
792
793 auto& style = this->style();
794 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
795 float wordSpacing = font.wordSpacing();
796 auto& string = text();
797 unsigned length = string.length();
798 auto iteratorMode = mapLineBreakToIteratorMode(style.lineBreak());
799 LazyLineBreakIterator breakIterator(string, style.locale(), iteratorMode);
800 bool needsWordSpacing = false;
801 bool ignoringSpaces = false;
802 bool isSpace = false;
803 bool firstWord = true;
804 bool firstLine = true;
805 Optional<unsigned> nextBreakable;
806 unsigned lastWordBoundary = 0;
807
808 WordTrailingSpace wordTrailingSpace(style);
809 // If automatic hyphenation is allowed, we keep track of the width of the widest word (or word
810 // fragment) encountered so far, and only try hyphenating words that are wider.
811 float maxWordWidth = std::numeric_limits<float>::max();
812 unsigned minimumPrefixLength = 0;
813 unsigned minimumSuffixLength = 0;
814 if (style.hyphens() == Hyphens::Auto && canHyphenate(style.locale())) {
815 maxWordWidth = 0;
816
817 // Map 'hyphenate-limit-{before,after}: auto;' to 2.
818 auto before = style.hyphenationLimitBefore();
819 minimumPrefixLength = before < 0 ? 2 : before;
820
821 auto after = style.hyphenationLimitAfter();
822 minimumSuffixLength = after < 0 ? 2 : after;
823 }
824
825 Optional<int> firstGlyphLeftOverflow;
826
827 bool breakNBSP = style.autoWrap() && style.nbspMode() == NBSPMode::Space;
828
829 // Note the deliberate omission of word-wrap and overflow-wrap from this breakAll check. Those
830 // do not affect minimum preferred sizes. Note that break-word is a non-standard value for
831 // word-break, but we support it as though it means break-all.
832 bool breakAnywhere = style.lineBreak() == LineBreak::Anywhere && style.autoWrap();
833 bool breakAll = (style.wordBreak() == WordBreak::BreakAll || style.wordBreak() == WordBreak::BreakWord) && style.autoWrap();
834 bool keepAllWords = style.wordBreak() == WordBreak::KeepAll;
835 bool canUseLineBreakShortcut = iteratorMode == LineBreakIteratorMode::Default;
836
837 for (unsigned i = 0; i < length; i++) {
838 UChar c = string[i];
839
840 bool previousCharacterIsSpace = isSpace;
841
842 bool isNewline = false;
843 if (c == '\n') {
844 if (style.preserveNewline()) {
845 m_hasBreak = true;
846 isNewline = true;
847 isSpace = false;
848 } else
849 isSpace = true;
850 } else if (c == '\t') {
851 if (!style.collapseWhiteSpace()) {
852 m_hasTab = true;
853 isSpace = false;
854 } else
855 isSpace = true;
856 } else
857 isSpace = c == ' ';
858
859 if ((isSpace || isNewline) && !i)
860 m_hasBeginWS = true;
861 if ((isSpace || isNewline) && i == length - 1)
862 m_hasEndWS = true;
863
864 ignoringSpaces |= style.collapseWhiteSpace() && previousCharacterIsSpace && isSpace;
865 ignoringSpaces &= isSpace;
866
867 // Ignore spaces and soft hyphens
868 if (ignoringSpaces) {
869 ASSERT(lastWordBoundary == i);
870 lastWordBoundary++;
871 continue;
872 } else if (c == softHyphen && style.hyphens() != Hyphens::None) {
873 ASSERT(i >= lastWordBoundary);
874 currMaxWidth += widthFromCache(font, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
875 if (!firstGlyphLeftOverflow)
876 firstGlyphLeftOverflow = glyphOverflow.left;
877 lastWordBoundary = i + 1;
878 continue;
879 }
880
881 bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP, canUseLineBreakShortcut, keepAllWords, breakAnywhere);
882 bool betweenWords = true;
883 unsigned j = i;
884 while (c != '\n' && !isSpaceAccordingToStyle(c, style) && c != '\t' && (c != softHyphen || style.hyphens() == Hyphens::None)) {
885 j++;
886 if (j == length)
887 break;
888 c = string[j];
889 if (isBreakable(breakIterator, j, nextBreakable, breakNBSP, canUseLineBreakShortcut, keepAllWords, breakAnywhere) && characterAt(j - 1) != softHyphen)
890 break;
891 if (breakAll) {
892 betweenWords = false;
893 break;
894 }
895 }
896
897 unsigned wordLen = j - i;
898 if (wordLen) {
899 float currMinWidth = 0;
900 bool isSpace = (j < length) && isSpaceAccordingToStyle(c, style);
901 float w;
902 Optional<float> wordTrailingSpaceWidth;
903 if (isSpace)
904 wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
905 if (wordTrailingSpaceWidth)
906 w = widthFromCache(font, i, wordLen + 1, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style) - wordTrailingSpaceWidth.value();
907 else {
908 w = widthFromCache(font, i, wordLen, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
909 if (c == softHyphen && style.hyphens() != Hyphens::None)
910 currMinWidth = hyphenWidth(*this, font);
911 }
912
913 if (w > maxWordWidth) {
914 unsigned suffixStart;
915 float maxFragmentWidth = maxWordFragmentWidth(*this, style, font, StringView(string).substring(i, wordLen), minimumPrefixLength, minimumSuffixLength, suffixStart, fallbackFonts, glyphOverflow);
916
917 if (suffixStart) {
918 float suffixWidth;
919 Optional<float> wordTrailingSpaceWidth;
920 if (isSpace)
921 wordTrailingSpaceWidth = wordTrailingSpace.width(fallbackFonts);
922 if (wordTrailingSpaceWidth)
923 suffixWidth = widthFromCache(font, i + suffixStart, wordLen - suffixStart + 1, leadWidth + currMaxWidth, 0, 0, style) - wordTrailingSpaceWidth.value();
924 else
925 suffixWidth = widthFromCache(font, i + suffixStart, wordLen - suffixStart, leadWidth + currMaxWidth, 0, 0, style);
926
927 maxFragmentWidth = std::max(maxFragmentWidth, suffixWidth);
928
929 currMinWidth += maxFragmentWidth - w;
930 maxWordWidth = std::max(maxWordWidth, maxFragmentWidth);
931 } else
932 maxWordWidth = w;
933 }
934
935 if (!firstGlyphLeftOverflow)
936 firstGlyphLeftOverflow = glyphOverflow.left;
937 currMinWidth += w;
938 if (betweenWords) {
939 if (lastWordBoundary == i)
940 currMaxWidth += w;
941 else {
942 ASSERT(j >= lastWordBoundary);
943 currMaxWidth += widthFromCache(font, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
944 }
945 lastWordBoundary = j;
946 }
947
948 bool isCollapsibleWhiteSpace = (j < length) && style.isCollapsibleWhiteSpace(c);
949 if (j < length && style.autoWrap())
950 m_hasBreakableChar = true;
951
952 // Add in wordSpacing to our currMaxWidth, but not if this is the last word on a line or the
953 // last word in the run.
954 if ((isSpace || isCollapsibleWhiteSpace) && !containsOnlyHTMLWhitespace(j, length - j))
955 currMaxWidth += wordSpacing;
956
957 if (firstWord) {
958 firstWord = false;
959 // If the first character in the run is breakable, then we consider ourselves to have a beginning
960 // minimum width of 0, since a break could occur right before our run starts, preventing us from ever
961 // being appended to a previous text run when considering the total minimum width of the containing block.
962 if (hasBreak)
963 m_hasBreakableChar = true;
964 m_beginMinWidth = hasBreak ? 0 : currMinWidth;
965 }
966 m_endMinWidth = currMinWidth;
967
968 m_minWidth = std::max(currMinWidth, m_minWidth);
969
970 i += wordLen - 1;
971 } else {
972 // Nowrap can never be broken, so don't bother setting the
973 // breakable character boolean. Pre can only be broken if we encounter a newline.
974 if (style.autoWrap() || isNewline)
975 m_hasBreakableChar = true;
976
977 if (isNewline) { // Only set if preserveNewline was true and we saw a newline.
978 if (firstLine) {
979 firstLine = false;
980 leadWidth = 0;
981 if (!style.autoWrap())
982 m_beginMinWidth = currMaxWidth;
983 }
984
985 if (currMaxWidth > m_maxWidth)
986 m_maxWidth = currMaxWidth;
987 currMaxWidth = 0;
988 } else {
989 TextRun run = RenderBlock::constructTextRun(*this, i, 1, style);
990 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
991 run.setXPos(leadWidth + currMaxWidth);
992
993 currMaxWidth += font.width(run, &fallbackFonts);
994 glyphOverflow.right = 0;
995 needsWordSpacing = isSpace && !previousCharacterIsSpace && i == length - 1;
996 }
997 ASSERT(lastWordBoundary == i);
998 lastWordBoundary++;
999 }
1000 }
1001
1002 glyphOverflow.left = firstGlyphLeftOverflow.valueOr(glyphOverflow.left);
1003
1004 if ((needsWordSpacing && length > 1) || (ignoringSpaces && !firstWord))
1005 currMaxWidth += wordSpacing;
1006
1007 m_maxWidth = std::max(currMaxWidth, m_maxWidth);
1008
1009 if (!style.autoWrap())
1010 m_minWidth = m_maxWidth;
1011
1012 if (style.whiteSpace() == WhiteSpace::Pre) {
1013 if (firstLine)
1014 m_beginMinWidth = m_maxWidth;
1015 m_endMinWidth = currMaxWidth;
1016 }
1017
1018 setPreferredLogicalWidthsDirty(false);
1019}
1020
1021template<typename CharacterType> static inline bool isAllCollapsibleWhitespace(const CharacterType* characters, unsigned length, const RenderStyle& style)
1022{
1023 for (unsigned i = 0; i < length; ++i) {
1024 if (!style.isCollapsibleWhiteSpace(characters[i]))
1025 return false;
1026 }
1027 return true;
1028}
1029
1030bool RenderText::isAllCollapsibleWhitespace() const
1031{
1032 if (text().is8Bit())
1033 return WebCore::isAllCollapsibleWhitespace(text().characters8(), text().length(), style());
1034 return WebCore::isAllCollapsibleWhitespace(text().characters16(), text().length(), style());
1035}
1036
1037template<typename CharacterType> static inline bool isAllPossiblyCollapsibleWhitespace(const CharacterType* characters, unsigned length)
1038{
1039 for (unsigned i = 0; i < length; ++i) {
1040 if (!(characters[i] == '\n' || characters[i] == ' ' || characters[i] == '\t'))
1041 return false;
1042 }
1043 return true;
1044}
1045
1046bool RenderText::containsOnlyHTMLWhitespace(unsigned from, unsigned length) const
1047{
1048 ASSERT(from <= text().length());
1049 ASSERT(length <= text().length());
1050 ASSERT(from + length <= text().length());
1051 if (text().is8Bit())
1052 return isAllPossiblyCollapsibleWhitespace(text().characters8() + from, length);
1053 return isAllPossiblyCollapsibleWhitespace(text().characters16() + from, length);
1054}
1055
1056Vector<std::pair<unsigned, unsigned>> RenderText::draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const
1057{
1058 if (!textNode())
1059 return { };
1060
1061 auto markers = document().markers().markersFor(*textNode(), DocumentMarker::DraggedContent);
1062 if (markers.isEmpty())
1063 return { };
1064
1065 Vector<std::pair<unsigned, unsigned>> draggedContentRanges;
1066 for (auto* marker : markers) {
1067 unsigned markerStart = std::max(marker->startOffset(), startOffset);
1068 unsigned markerEnd = std::min(marker->endOffset(), endOffset);
1069 if (markerStart >= markerEnd || markerStart > endOffset || markerEnd < startOffset)
1070 continue;
1071
1072 std::pair<unsigned, unsigned> draggedContentRange;
1073 draggedContentRange.first = markerStart;
1074 draggedContentRange.second = markerEnd;
1075 draggedContentRanges.append(draggedContentRange);
1076 }
1077 return draggedContentRanges;
1078}
1079
1080IntPoint RenderText::firstRunLocation() const
1081{
1082 if (auto* layout = simpleLineLayout())
1083 return SimpleLineLayout::computeFirstRunLocation(*this, *layout);
1084
1085 return m_lineBoxes.firstRunLocation();
1086}
1087
1088void RenderText::setSelectionState(SelectionState state)
1089{
1090 if (state != SelectionNone)
1091 ensureLineBoxes();
1092
1093 RenderObject::setSelectionState(state);
1094
1095 if (canUpdateSelectionOnRootLineBoxes())
1096 m_lineBoxes.setSelectionState(*this, state);
1097
1098 // The containing block can be null in case of an orphaned tree.
1099 RenderBlock* containingBlock = this->containingBlock();
1100 if (containingBlock && !containingBlock->isRenderView())
1101 containingBlock->setSelectionState(state);
1102}
1103
1104void RenderText::setTextWithOffset(const String& newText, unsigned offset, unsigned length, bool force)
1105{
1106 if (!force && text() == newText)
1107 return;
1108
1109 int delta = newText.length() - text().length();
1110 unsigned end = length ? offset + length - 1 : offset;
1111
1112 m_linesDirty = simpleLineLayout() || m_lineBoxes.dirtyRange(*this, offset, end, delta);
1113
1114 setText(newText, force || m_linesDirty);
1115}
1116
1117static inline bool isInlineFlowOrEmptyText(const RenderObject& renderer)
1118{
1119 return is<RenderInline>(renderer) || (is<RenderText>(renderer) && downcast<RenderText>(renderer).text().isEmpty());
1120}
1121
1122UChar RenderText::previousCharacter() const
1123{
1124 // find previous text renderer if one exists
1125 const RenderObject* previousText = this;
1126 while ((previousText = previousText->previousInPreOrder())) {
1127 if (!isInlineFlowOrEmptyText(*previousText))
1128 break;
1129 }
1130 if (!is<RenderText>(previousText))
1131 return ' ';
1132 auto& previousString = downcast<RenderText>(*previousText).text();
1133 return previousString[previousString.length() - 1];
1134}
1135
1136LayoutUnit RenderText::topOfFirstText() const
1137{
1138 return firstTextBox()->root().lineTop();
1139}
1140
1141String applyTextTransform(const RenderStyle& style, const String& text, UChar previousCharacter)
1142{
1143 switch (style.textTransform()) {
1144 case TextTransform::None:
1145 return text;
1146 case TextTransform::Capitalize:
1147 return capitalize(text, previousCharacter); // FIXME: Need to take locale into account.
1148 case TextTransform::Uppercase:
1149 return text.convertToUppercaseWithLocale(style.locale());
1150 case TextTransform::Lowercase:
1151 return text.convertToLowercaseWithLocale(style.locale());
1152 }
1153 ASSERT_NOT_REACHED();
1154 return text;
1155}
1156
1157void RenderText::setRenderedText(const String& newText)
1158{
1159 ASSERT(!newText.isNull());
1160
1161 String originalText = this->originalText();
1162
1163 m_text = newText;
1164
1165 if (m_useBackslashAsYenSymbol)
1166 m_text.replace('\\', yenSign);
1167
1168 const auto& style = this->style();
1169 if (style.textTransform() != TextTransform::None)
1170 m_text = applyTextTransform(style, m_text, previousCharacter());
1171
1172 switch (style.textSecurity()) {
1173 case TextSecurity::None:
1174 break;
1175#if !PLATFORM(IOS_FAMILY)
1176 // We use the same characters here as for list markers.
1177 // See the listMarkerText function in RenderListMarker.cpp.
1178 case TextSecurity::Circle:
1179 secureText(whiteBullet);
1180 break;
1181 case TextSecurity::Disc:
1182 secureText(bullet);
1183 break;
1184 case TextSecurity::Square:
1185 secureText(blackSquare);
1186 break;
1187#else
1188 // FIXME: Why this quirk on iOS?
1189 case TextSecurity::Circle:
1190 case TextSecurity::Disc:
1191 case TextSecurity::Square:
1192 secureText(blackCircle);
1193 break;
1194#endif
1195 }
1196
1197 m_isAllASCII = text().isAllASCII();
1198 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
1199 m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
1200
1201 if (m_text != originalText) {
1202 originalTextMap().set(this, originalText);
1203 m_originalTextDiffersFromRendered = true;
1204 } else if (m_originalTextDiffersFromRendered) {
1205 originalTextMap().remove(this);
1206 m_originalTextDiffersFromRendered = false;
1207 }
1208}
1209
1210void RenderText::secureText(UChar maskingCharacter)
1211{
1212 // This hides the text by replacing all the characters with the masking character.
1213 // Offsets within the hidden text have to match offsets within the original text
1214 // to handle things like carets and selection, so this won't work right if any
1215 // of the characters are surrogate pairs or combining marks. Thus, this function
1216 // does not attempt to handle either of those.
1217
1218 unsigned length = text().length();
1219 if (!length)
1220 return;
1221
1222 UChar characterToReveal = 0;
1223 unsigned revealedCharactersOffset = 0;
1224
1225 if (SecureTextTimer* timer = secureTextTimers().get(this)) {
1226 // We take the offset out of the timer to make this one-shot. We count on this being called only once.
1227 // If it's called a second time we assume the text is different and a character should not be revealed.
1228 revealedCharactersOffset = timer->takeOffsetAfterLastTypedCharacter();
1229 if (revealedCharactersOffset && revealedCharactersOffset <= length)
1230 characterToReveal = text()[--revealedCharactersOffset];
1231 }
1232
1233 UChar* characters;
1234 m_text = String::createUninitialized(length, characters);
1235
1236 for (unsigned i = 0; i < length; ++i)
1237 characters[i] = maskingCharacter;
1238 if (characterToReveal)
1239 characters[revealedCharactersOffset] = characterToReveal;
1240}
1241
1242bool RenderText::computeCanUseSimplifiedTextMeasuring() const
1243{
1244 if (!m_canUseSimpleFontCodePath)
1245 return false;
1246
1247 auto& font = style().fontCascade();
1248 if (font.wordSpacing() || font.letterSpacing())
1249 return false;
1250
1251 // Additional check on the font codepath.
1252 TextRun run(m_text);
1253 run.setCharacterScanForCodePath(false);
1254 if (font.codePath(run) != FontCascade::Simple)
1255 return false;
1256
1257 auto whitespaceIsCollapsed = style().collapseWhiteSpace();
1258 for (unsigned i = 0; i < text().length(); ++i) {
1259 if ((!whitespaceIsCollapsed && text()[i] == '\t') || text()[i] == noBreakSpace || text()[i] >= HiraganaLetterSmallA)
1260 return false;
1261 }
1262 return true;
1263}
1264
1265void RenderText::setText(const String& text, bool force)
1266{
1267 ASSERT(!text.isNull());
1268
1269 if (!force && text == originalText())
1270 return;
1271
1272 m_text = text;
1273 if (m_originalTextDiffersFromRendered) {
1274 originalTextMap().remove(this);
1275 m_originalTextDiffersFromRendered = false;
1276 }
1277
1278 setRenderedText(text);
1279
1280 setNeedsLayoutAndPrefWidthsRecalc();
1281 m_knownToHaveNoOverflowAndNoFallbackFonts = false;
1282
1283 if (is<RenderBlockFlow>(*parent()))
1284 downcast<RenderBlockFlow>(*parent()).invalidateLineLayoutPath();
1285
1286 if (AXObjectCache* cache = document().existingAXObjectCache())
1287 cache->deferTextChangedIfNeeded(textNode());
1288}
1289
1290String RenderText::textWithoutConvertingBackslashToYenSymbol() const
1291{
1292 if (!m_useBackslashAsYenSymbol || style().textSecurity() != TextSecurity::None)
1293 return text();
1294
1295 if (style().textTransform() == TextTransform::None)
1296 return originalText();
1297
1298 return applyTextTransform(style(), originalText(), previousCharacter());
1299}
1300
1301void RenderText::dirtyLineBoxes(bool fullLayout)
1302{
1303 if (fullLayout)
1304 m_lineBoxes.deleteAll();
1305 else if (!m_linesDirty)
1306 m_lineBoxes.dirtyAll();
1307 m_linesDirty = false;
1308}
1309
1310std::unique_ptr<InlineTextBox> RenderText::createTextBox()
1311{
1312 return std::make_unique<InlineTextBox>(*this);
1313}
1314
1315void RenderText::positionLineBox(InlineTextBox& textBox)
1316{
1317 if (!textBox.hasTextContent())
1318 return;
1319 m_containsReversedText |= !textBox.isLeftToRightDirection();
1320}
1321
1322void RenderText::ensureLineBoxes()
1323{
1324 if (!is<RenderBlockFlow>(*parent()))
1325 return;
1326 downcast<RenderBlockFlow>(*parent()).ensureLineBoxes();
1327}
1328
1329const SimpleLineLayout::Layout* RenderText::simpleLineLayout() const
1330{
1331 if (!is<RenderBlockFlow>(*parent()))
1332 return nullptr;
1333 return downcast<RenderBlockFlow>(*parent()).simpleLineLayout();
1334}
1335
1336float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
1337{
1338 if (from >= text().length())
1339 return 0;
1340
1341 if (from + len > text().length())
1342 len = text().length() - from;
1343
1344 const RenderStyle& lineStyle = firstLine ? firstLineStyle() : style();
1345 return width(from, len, lineStyle.fontCascade(), xPos, fallbackFonts, glyphOverflow);
1346}
1347
1348float RenderText::width(unsigned from, unsigned len, const FontCascade& f, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
1349{
1350 ASSERT(from + len <= text().length());
1351 if (!text().length())
1352 return 0;
1353
1354 const RenderStyle& style = this->style();
1355 float w;
1356 if (&f == &style.fontCascade()) {
1357 if (!style.preserveNewline() && !from && len == text().length() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
1358 if (fallbackFonts) {
1359 ASSERT(glyphOverflow);
1360 if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts) {
1361 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0, *fallbackFonts, *glyphOverflow);
1362 if (fallbackFonts->isEmpty() && !glyphOverflow->left && !glyphOverflow->right && !glyphOverflow->top && !glyphOverflow->bottom)
1363 m_knownToHaveNoOverflowAndNoFallbackFonts = true;
1364 }
1365 w = m_maxWidth;
1366 } else
1367 w = maxLogicalWidth();
1368 } else
1369 w = widthFromCache(f, from, len, xPos, fallbackFonts, glyphOverflow, style);
1370 } else {
1371 TextRun run = RenderBlock::constructTextRun(*this, from, len, style);
1372 run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
1373 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1374 run.setXPos(xPos);
1375
1376 w = f.width(run, fallbackFonts, glyphOverflow);
1377 }
1378
1379 return w;
1380}
1381
1382IntRect RenderText::linesBoundingBox() const
1383{
1384 if (auto* layout = simpleLineLayout())
1385 return SimpleLineLayout::computeBoundingBox(*this, *layout);
1386
1387 return m_lineBoxes.boundingBox(*this);
1388}
1389
1390LayoutRect RenderText::linesVisualOverflowBoundingBox() const
1391{
1392 ASSERT(!simpleLineLayout());
1393 return m_lineBoxes.visualOverflowBoundingBox(*this);
1394}
1395
1396LayoutRect RenderText::clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const
1397{
1398 RenderObject* rendererToRepaint = containingBlock();
1399
1400 // Do not cross self-painting layer boundaries.
1401 RenderObject& enclosingLayerRenderer = enclosingLayer()->renderer();
1402 if (&enclosingLayerRenderer != rendererToRepaint && !rendererToRepaint->isDescendantOf(&enclosingLayerRenderer))
1403 rendererToRepaint = &enclosingLayerRenderer;
1404
1405 // The renderer we chose to repaint may be an ancestor of repaintContainer, but we need to do a repaintContainer-relative repaint.
1406 if (repaintContainer && repaintContainer != rendererToRepaint && !rendererToRepaint->isDescendantOf(repaintContainer))
1407 return repaintContainer->clippedOverflowRectForRepaint(repaintContainer);
1408
1409 return rendererToRepaint->clippedOverflowRectForRepaint(repaintContainer);
1410}
1411
1412LayoutRect RenderText::collectSelectionRectsForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<LayoutRect>* rects)
1413{
1414 ASSERT(!needsLayout());
1415 ASSERT(!simpleLineLayout());
1416
1417 if (selectionState() == SelectionNone)
1418 return LayoutRect();
1419 if (!containingBlock())
1420 return LayoutRect();
1421
1422 // Now calculate startPos and endPos for painting selection.
1423 // We include a selection while endPos > 0
1424 unsigned startPos;
1425 unsigned endPos;
1426 if (selectionState() == SelectionInside) {
1427 // We are fully selected.
1428 startPos = 0;
1429 endPos = text().length();
1430 } else {
1431 startPos = view().selection().startPosition();
1432 endPos = view().selection().endPosition();
1433 if (selectionState() == SelectionStart)
1434 endPos = text().length();
1435 else if (selectionState() == SelectionEnd)
1436 startPos = 0;
1437 }
1438
1439 if (startPos == endPos)
1440 return IntRect();
1441
1442 LayoutRect resultRect;
1443 if (!rects)
1444 resultRect = m_lineBoxes.selectionRectForRange(startPos, endPos);
1445 else {
1446 m_lineBoxes.collectSelectionRectsForRange(startPos, endPos, *rects);
1447 for (auto& rect : *rects) {
1448 resultRect.unite(rect);
1449 rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
1450 }
1451 }
1452
1453 if (clipToVisibleContent)
1454 return computeRectForRepaint(resultRect, repaintContainer);
1455 return localToContainerQuad(FloatRect(resultRect), repaintContainer).enclosingBoundingBox();
1456}
1457
1458LayoutRect RenderText::collectSelectionRectsForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<LayoutRect>& rects)
1459{
1460 return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, &rects);
1461}
1462
1463LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent)
1464{
1465 return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, nullptr);
1466}
1467
1468int RenderText::caretMinOffset() const
1469{
1470 if (auto* layout = simpleLineLayout())
1471 return SimpleLineLayout::findCaretMinimumOffset(*this, *layout);
1472 return m_lineBoxes.caretMinOffset();
1473}
1474
1475int RenderText::caretMaxOffset() const
1476{
1477 if (auto* layout = simpleLineLayout())
1478 return SimpleLineLayout::findCaretMaximumOffset(*this, *layout);
1479 return m_lineBoxes.caretMaxOffset(*this);
1480}
1481
1482unsigned RenderText::countRenderedCharacterOffsetsUntil(unsigned offset) const
1483{
1484 ASSERT(!simpleLineLayout());
1485 return m_lineBoxes.countCharacterOffsetsUntil(offset);
1486}
1487
1488bool RenderText::containsRenderedCharacterOffset(unsigned offset) const
1489{
1490 ASSERT(!simpleLineLayout());
1491 return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CharacterOffset);
1492}
1493
1494bool RenderText::containsCaretOffset(unsigned offset) const
1495{
1496 if (auto* layout = simpleLineLayout())
1497 return SimpleLineLayout::containsCaretOffset(*this, *layout, offset);
1498 return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CaretOffset);
1499}
1500
1501bool RenderText::hasRenderedText() const
1502{
1503 if (auto* layout = simpleLineLayout())
1504 return SimpleLineLayout::isTextRendered(*this, *layout);
1505 return m_lineBoxes.hasRenderedText();
1506}
1507
1508int RenderText::previousOffset(int current) const
1509{
1510 if (m_isAllASCII || text().is8Bit())
1511 return current - 1;
1512
1513 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Caret, nullAtom());
1514 return iterator.preceding(current).valueOr(current - 1);
1515}
1516
1517int RenderText::previousOffsetForBackwardDeletion(int current) const
1518{
1519 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Delete, nullAtom());
1520 return iterator.preceding(current).valueOr(0);
1521}
1522
1523int RenderText::nextOffset(int current) const
1524{
1525 if (m_isAllASCII || text().is8Bit())
1526 return current + 1;
1527
1528 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Caret, nullAtom());
1529 return iterator.following(current).valueOr(current + 1);
1530}
1531
1532bool RenderText::computeCanUseSimpleFontCodePath() const
1533{
1534 if (m_isAllASCII || text().is8Bit())
1535 return true;
1536 return FontCascade::characterRangeCodePath(text().characters16(), length()) == FontCascade::Simple;
1537}
1538
1539void RenderText::momentarilyRevealLastTypedCharacter(unsigned offsetAfterLastTypedCharacter)
1540{
1541 if (style().textSecurity() == TextSecurity::None)
1542 return;
1543 auto& secureTextTimer = secureTextTimers().add(this, nullptr).iterator->value;
1544 if (!secureTextTimer)
1545 secureTextTimer = std::make_unique<SecureTextTimer>(*this);
1546 secureTextTimer->restart(offsetAfterLastTypedCharacter);
1547}
1548
1549StringView RenderText::stringView(unsigned start, Optional<unsigned> stop) const
1550{
1551 unsigned destination = stop.valueOr(text().length());
1552 ASSERT(start <= length());
1553 ASSERT(destination <= length());
1554 ASSERT(start <= destination);
1555 if (text().is8Bit())
1556 return { text().characters8() + start, destination - start };
1557 return { text().characters16() + start, destination - start };
1558}
1559
1560RenderInline* RenderText::inlineWrapperForDisplayContents()
1561{
1562 ASSERT(m_hasInlineWrapperForDisplayContents == inlineWrapperForDisplayContentsMap().contains(this));
1563
1564 if (!m_hasInlineWrapperForDisplayContents)
1565 return nullptr;
1566 return inlineWrapperForDisplayContentsMap().get(this).get();
1567}
1568
1569void RenderText::setInlineWrapperForDisplayContents(RenderInline* wrapper)
1570{
1571 ASSERT(m_hasInlineWrapperForDisplayContents == inlineWrapperForDisplayContentsMap().contains(this));
1572
1573 if (!wrapper) {
1574 if (!m_hasInlineWrapperForDisplayContents)
1575 return;
1576 inlineWrapperForDisplayContentsMap().remove(this);
1577 m_hasInlineWrapperForDisplayContents = false;
1578 return;
1579 }
1580 inlineWrapperForDisplayContentsMap().add(this, makeWeakPtr(wrapper));
1581 m_hasInlineWrapperForDisplayContents = true;
1582}
1583
1584RenderText* RenderText::findByDisplayContentsInlineWrapperCandidate(RenderElement& renderer)
1585{
1586 auto* firstChild = renderer.firstChild();
1587 if (!is<RenderText>(firstChild))
1588 return nullptr;
1589 auto& textRenderer = downcast<RenderText>(*firstChild);
1590 if (textRenderer.inlineWrapperForDisplayContents() != &renderer)
1591 return nullptr;
1592 ASSERT(textRenderer.textNode());
1593 ASSERT(renderer.firstChild() == renderer.lastChild());
1594 return &textRenderer;
1595
1596}
1597
1598} // namespace WebCore
1599