source: webkit/trunk/Source/WebCore/rendering/RenderText.cpp

Last change on this file was 295573, checked in by Antti Koivisto, 3 years ago

REGRESSION (r287195): Safari fails to correctly render indented numbered lists with custom CSS and hyphenation ON
https://bugs.webkit.org/show_bug.cgi?id=241630
<rdar://91245970>

Reviewed by Darin Adler.

  • LayoutTests/fast/text/list-and-hyphenation-expected.html: Added.
  • LayoutTests/fast/text/list-and-hyphenation.html: Added.
  • Source/WebCore/rendering/RenderText.cpp:

(WebCore::RenderText::maxWordFragmentWidth):

Return entireWordWidth instead of 0 in the bail out cases.
This ends up setting maxWordWidth = w and leaving currMinWidth unchanged in the caller, matching the pre-r287195 behavior.

Canonical link: https://commits.webkit.org/251578@main

  • Property svn:eol-style set to native
File size: 69.2 KB
Line 
1/*
2 * (C) 1999 Lars Knoll ([email protected])
3 * (C) 2000 Dirk Mueller ([email protected])
4 * Copyright (C) 2004-2021 Apple Inc. All rights reserved.
5 * Copyright (C) 2006 Andrew Wellington ([email protected])
6 * Copyright (C) 2006 Graham Dennis ([email protected])
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 "DocumentInlines.h"
33#include "DocumentMarkerController.h"
34#include "FloatQuad.h"
35#include "Frame.h"
36#include "FrameView.h"
37#include "HTMLParserIdioms.h"
38#include "Hyphenation.h"
39#include "InlineIteratorLineBox.h"
40#include "InlineIteratorLogicalOrderTraversal.h"
41#include "InlineIteratorTextBox.h"
42#include "InlineRunAndOffset.h"
43#include "LayoutIntegrationLineLayout.h"
44#include "LegacyEllipsisBox.h"
45#include "LineSelection.h"
46#include "Range.h"
47#include "RenderBlock.h"
48#include "RenderCombineText.h"
49#include "RenderInline.h"
50#include "RenderLayer.h"
51#include "RenderTextInlines.h"
52#include "RenderView.h"
53#include "RenderedDocumentMarker.h"
54#include "SVGElementTypeHelpers.h"
55#include "SVGInlineTextBox.h"
56#include "Settings.h"
57#include "Text.h"
58#include "TextResourceDecoder.h"
59#include "VisiblePosition.h"
60#include "WidthIterator.h"
61#include <wtf/IsoMallocInlines.h>
62#include <wtf/NeverDestroyed.h>
63#include <wtf/text/StringBuilder.h>
64#include <wtf/text/TextBreakIterator.h>
65#include <wtf/unicode/CharacterNames.h>
66
67#if PLATFORM(IOS_FAMILY)
68#include "Document.h"
69#include "EditorClient.h"
70#include "LogicalSelectionOffsetCaches.h"
71#include "Page.h"
72#include "SelectionGeometry.h"
73#endif
74
75namespace WebCore {
76
77using namespace WTF::Unicode;
78
79WTF_MAKE_ISO_ALLOCATED_IMPL(RenderText);
80
81struct SameSizeAsRenderText : public RenderObject {
82 void* pointers[2];
83 uint32_t bitfields : 16;
84#if ENABLE(TEXT_AUTOSIZING)
85 float candidateTextSize;
86#endif
87 float widths[4];
88 String text;
89};
90
91static_assert(sizeof(RenderText) == sizeof(SameSizeAsRenderText), "RenderText should stay small");
92
93class SecureTextTimer final : private TimerBase {
94 WTF_MAKE_FAST_ALLOCATED;
95public:
96 explicit SecureTextTimer(RenderText&);
97 void restart(unsigned offsetAfterLastTypedCharacter);
98
99 unsigned takeOffsetAfterLastTypedCharacter();
100
101private:
102 void fired() override;
103 RenderText& m_renderer;
104 unsigned m_offsetAfterLastTypedCharacter { 0 };
105};
106
107typedef HashMap<RenderText*, std::unique_ptr<SecureTextTimer>> SecureTextTimerMap;
108
109static SecureTextTimerMap& secureTextTimers()
110{
111 static NeverDestroyed<SecureTextTimerMap> map;
112 return map.get();
113}
114
115inline SecureTextTimer::SecureTextTimer(RenderText& renderer)
116 : m_renderer(renderer)
117{
118}
119
120inline void SecureTextTimer::restart(unsigned offsetAfterLastTypedCharacter)
121{
122 m_offsetAfterLastTypedCharacter = offsetAfterLastTypedCharacter;
123 startOneShot(1_s * m_renderer.settings().passwordEchoDurationInSeconds());
124}
125
126inline unsigned SecureTextTimer::takeOffsetAfterLastTypedCharacter()
127{
128 unsigned offset = m_offsetAfterLastTypedCharacter;
129 m_offsetAfterLastTypedCharacter = 0;
130 return offset;
131}
132
133void SecureTextTimer::fired()
134{
135 ASSERT(secureTextTimers().get(&m_renderer) == this);
136 m_offsetAfterLastTypedCharacter = 0;
137 m_renderer.setText(m_renderer.text(), true /* forcing setting text as it may be masked later */);
138}
139
140static HashMap<const RenderText*, String>& originalTextMap()
141{
142 static NeverDestroyed<HashMap<const RenderText*, String>> map;
143 return map;
144}
145
146static HashMap<const RenderText*, WeakPtr<RenderInline>>& inlineWrapperForDisplayContentsMap()
147{
148 static NeverDestroyed<HashMap<const RenderText*, WeakPtr<RenderInline>>> map;
149 return map;
150}
151
152static constexpr UChar convertNoBreakSpaceToSpace(UChar character)
153{
154 return character == noBreakSpace ? ' ' : character;
155}
156
157String capitalize(const String& string, UChar previousCharacter)
158{
159 // FIXME: Change this to use u_strToTitle instead of u_totitle and to consider locale.
160
161 unsigned length = string.length();
162 auto& stringImpl = *string.impl();
163
164 static_assert(String::MaxLength < std::numeric_limits<unsigned>::max(), "Must be able to add one without overflowing unsigned");
165
166 // Replace NO BREAK SPACE with a normal spaces since ICU does not treat it as a word separator.
167 Vector<UChar> stringWithPrevious(length + 1);
168 stringWithPrevious[0] = convertNoBreakSpaceToSpace(previousCharacter);
169 for (unsigned i = 1; i < length + 1; i++)
170 stringWithPrevious[i] = convertNoBreakSpaceToSpace(stringImpl[i - 1]);
171
172 auto* breakIterator = wordBreakIterator(StringView { stringWithPrevious.data(), length + 1 });
173 if (!breakIterator)
174 return string;
175
176 StringBuilder result;
177 result.reserveCapacity(length);
178
179 int32_t startOfWord = ubrk_first(breakIterator);
180 int32_t endOfWord;
181 for (endOfWord = ubrk_next(breakIterator); endOfWord != UBRK_DONE; startOfWord = endOfWord, endOfWord = ubrk_next(breakIterator)) {
182 if (startOfWord) // Do not append the first character, since it's the previous character, not from this string.
183 result.appendCharacter(u_totitle(stringImpl[startOfWord - 1]));
184 for (int i = startOfWord + 1; i < endOfWord; i++)
185 result.append(stringImpl[i - 1]);
186 }
187
188 return result == string ? string : result.toString();
189}
190
191inline RenderText::RenderText(Node& node, const String& text)
192 : RenderObject(node)
193 , m_hasTab(false)
194 , m_linesDirty(false)
195 , m_needsVisualReordering(false)
196 , m_isAllASCII(text.impl()->isAllASCII())
197 , m_knownToHaveNoOverflowAndNoFallbackFonts(false)
198 , m_useBackslashAsYenSymbol(false)
199 , m_originalTextDiffersFromRendered(false)
200 , m_hasInlineWrapperForDisplayContents(false)
201 , m_text(text)
202{
203 ASSERT(!m_text.isNull());
204 setIsText();
205 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
206}
207
208RenderText::RenderText(Text& textNode, const String& text)
209 : RenderText(static_cast<Node&>(textNode), text)
210{
211}
212
213RenderText::RenderText(Document& document, const String& text)
214 : RenderText(static_cast<Node&>(document), text)
215{
216}
217
218RenderText::~RenderText()
219{
220 // Do not add any code here. Add it to willBeDestroyed() instead.
221 ASSERT(!originalTextMap().contains(this));
222}
223
224ASCIILiteral RenderText::renderName() const
225{
226 return "RenderText"_s;
227}
228
229Text* RenderText::textNode() const
230{
231 return downcast<Text>(RenderObject::node());
232}
233
234bool RenderText::isTextFragment() const
235{
236 return false;
237}
238
239bool RenderText::computeUseBackslashAsYenSymbol() const
240{
241 const RenderStyle& style = this->style();
242 const auto& fontDescription = style.fontDescription();
243 if (style.fontCascade().useBackslashAsYenSymbol())
244 return true;
245 if (fontDescription.isSpecifiedFont())
246 return false;
247 const PAL::TextEncoding* encoding = document().decoder() ? &document().decoder()->encoding() : 0;
248 if (encoding && encoding->backslashAsCurrencySymbol() != '\\')
249 return true;
250 return false;
251}
252
253void RenderText::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
254{
255 // There is no need to ever schedule repaints from a style change of a text run, since
256 // we already did this for the parent of the text run.
257 // We do have to schedule layouts, though, since a style change can force us to
258 // need to relayout.
259 if (diff == StyleDifference::Layout) {
260 setNeedsLayoutAndPrefWidthsRecalc();
261 m_knownToHaveNoOverflowAndNoFallbackFonts = false;
262 }
263
264 const RenderStyle& newStyle = style();
265 bool needsResetText = false;
266 if (!oldStyle) {
267 m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
268 needsResetText = m_useBackslashAsYenSymbol;
269 } else if (oldStyle->fontCascade().useBackslashAsYenSymbol() != newStyle.fontCascade().useBackslashAsYenSymbol()) {
270 m_useBackslashAsYenSymbol = computeUseBackslashAsYenSymbol();
271 needsResetText = true;
272 }
273
274 if (!oldStyle || oldStyle->fontCascade() != newStyle.fontCascade())
275 m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
276
277 TextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TextTransform::None;
278 TextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TextSecurity::None;
279 if (needsResetText || oldTransform != newStyle.textTransform() || oldSecurity != newStyle.textSecurity())
280 RenderText::setText(originalText(), true);
281}
282
283void RenderText::removeAndDestroyTextBoxes()
284{
285 if (!renderTreeBeingDestroyed())
286 m_lineBoxes.removeAllFromParent(*this);
287#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
288 else
289 m_lineBoxes.invalidateParentChildLists();
290#endif
291 deleteLineBoxes();
292}
293
294void RenderText::willBeDestroyed()
295{
296 secureTextTimers().remove(this);
297
298 removeAndDestroyTextBoxes();
299
300 if (m_originalTextDiffersFromRendered)
301 originalTextMap().remove(this);
302
303 setInlineWrapperForDisplayContents(nullptr);
304
305 RenderObject::willBeDestroyed();
306}
307
308String RenderText::originalText() const
309{
310 return m_originalTextDiffersFromRendered ? originalTextMap().get(this) : m_text;
311}
312
313void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const
314{
315 for (auto& run : InlineIterator::textBoxesFor(*this)) {
316 auto rect = run.visualRectIgnoringBlockDirection();
317 rects.append(enclosingIntRect(FloatRect(accumulatedOffset + rect.location(), rect.size())));
318 }
319}
320
321Vector<IntRect> RenderText::absoluteRectsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
322{
323 return absoluteQuadsForRange(start, end, useSelectionHeight, false /* ignoreEmptyTextSelections */, wasFixed).map([](auto& quad) {
324 return quad.enclosingBoundingBox();
325 });
326}
327
328#if PLATFORM(IOS_FAMILY)
329// This function is similar in spirit to addLineBoxRects, but returns rectangles
330// which are annotated with additional state which helps the iPhone draw selections in its unique way.
331// Full annotations are added in this class.
332void RenderText::collectSelectionGeometries(Vector<SelectionGeometry>& rects, unsigned start, unsigned end)
333{
334 for (auto run = InlineIterator::firstTextBoxFor(*this); run; run = run.traverseNextTextBox()) {
335 LayoutRect rect;
336 if (start <= run->start() && run->end() <= end)
337 rect = run->selectionRect(start, end);
338 else {
339 unsigned realEnd = std::min(run->end(), end);
340 rect = run->selectionRect(start, realEnd);
341 if (rect.isEmpty())
342 continue;
343 }
344
345 if (run->lineBox()->isFirstAfterPageBreak()) {
346 if (run->isHorizontal())
347 rect.shiftYEdgeTo(run->lineBox()->top());
348 else
349 rect.shiftXEdgeTo(run->lineBox()->top());
350 }
351
352 RenderBlock* containingBlock = this->containingBlock();
353 // Map rect, extended left to leftOffset, and right to rightOffset, through transforms to get minX and maxX.
354 LogicalSelectionOffsetCaches cache(*containingBlock);
355 LayoutUnit leftOffset = containingBlock->logicalLeftSelectionOffset(*containingBlock, LayoutUnit(run->logicalTop()), cache);
356 LayoutUnit rightOffset = containingBlock->logicalRightSelectionOffset(*containingBlock, LayoutUnit(run->logicalTop()), cache);
357 LayoutRect extentsRect = rect;
358 if (run->isHorizontal()) {
359 extentsRect.setX(leftOffset);
360 extentsRect.setWidth(rightOffset - leftOffset);
361 } else {
362 extentsRect.setY(leftOffset);
363 extentsRect.setHeight(rightOffset - leftOffset);
364 }
365 extentsRect = localToAbsoluteQuad(FloatRect(extentsRect)).enclosingBoundingBox();
366 if (!run->isHorizontal())
367 extentsRect = extentsRect.transposedRect();
368 bool isFirstOnLine = !run->previousOnLine();
369 bool isLastOnLine = !run->nextOnLine();
370 if (containingBlock->isRubyBase() || containingBlock->isRubyText())
371 isLastOnLine = !containingBlock->containingBlock()->inlineBoxWrapper()->nextOnLineExists();
372
373 bool containsStart = run->start() <= start && run->end() >= start;
374 bool containsEnd = run->start() <= end && run->end() >= end;
375
376 bool isFixed = false;
377 auto absoluteQuad = localToAbsoluteQuad(FloatRect(rect), UseTransforms, &isFixed);
378 bool boxIsHorizontal = !is<SVGInlineTextBox>(run->legacyInlineBox()) ? run->isHorizontal() : !style().isVerticalWritingMode();
379 // If the containing block is an inline element, we want to check the inlineBoxWrapper orientation
380 // to determine the orientation of the block. In this case we also use the inlineBoxWrapper to
381 // determine if the element is the last on the line.
382 if (containingBlock->inlineBoxWrapper()) {
383 if (containingBlock->inlineBoxWrapper()->isHorizontal() != boxIsHorizontal) {
384 boxIsHorizontal = containingBlock->inlineBoxWrapper()->isHorizontal();
385 isLastOnLine = !containingBlock->inlineBoxWrapper()->nextOnLineExists();
386 }
387 }
388
389 rects.append(SelectionGeometry(absoluteQuad, HTMLElement::selectionRenderingBehavior(textNode()), run->direction(), extentsRect.x(), extentsRect.maxX(), extentsRect.maxY(), 0, run->isLineBreak(), isFirstOnLine, isLastOnLine, containsStart, containsEnd, boxIsHorizontal, isFixed, containingBlock->isRubyText(), view().pageNumberForBlockProgressionOffset(absoluteQuad.enclosingBoundingBox().x())));
390 }
391}
392#endif
393
394static FloatRect boundariesForTextRun(const InlineIterator::TextBox& run)
395{
396 if (is<SVGInlineTextBox>(run.legacyInlineBox()))
397 return downcast<SVGInlineTextBox>(*run.legacyInlineBox()).calculateBoundaries();
398
399 return run.visualRectIgnoringBlockDirection();
400}
401
402static IntRect ellipsisRectForTextRun(const InlineIterator::TextBox& run, unsigned start, unsigned end)
403{
404 // FIXME: No ellipsis support in modern path yet.
405 if (!run.legacyInlineBox())
406 return { };
407
408 auto& box = *run.legacyInlineBox();
409
410 auto truncation = box.truncation();
411 if (!truncation)
412 return { };
413
414 auto ellipsis = box.root().ellipsisBox();
415 if (!ellipsis)
416 return { };
417
418 int ellipsisStartPosition = std::max<int>(start - box.start(), 0);
419 int ellipsisEndPosition = std::min<int>(end - box.start(), box.len());
420
421 // The ellipsis should be considered to be selected if the end of
422 // the selection is past the beginning of the truncation and the
423 // beginning of the selection is before or at the beginning of the truncation.
424 if (ellipsisEndPosition < *truncation && ellipsisStartPosition > *truncation)
425 return { };
426
427 return ellipsis->selectionRect();
428}
429
430enum class ClippingOption { NoClipping, ClipToEllipsis };
431
432// FIXME: Unify with absoluteQuadsForRange.
433static Vector<FloatQuad> collectAbsoluteQuads(const RenderText& textRenderer, bool* wasFixed, ClippingOption clipping)
434{
435 Vector<FloatQuad> quads;
436 for (auto& run : InlineIterator::textBoxesFor(textRenderer)) {
437 auto boundaries = boundariesForTextRun(run);
438
439 // Shorten the width of this text box if it ends in an ellipsis.
440 if (clipping == ClippingOption::ClipToEllipsis) {
441 auto ellipsisRect = ellipsisRectForTextRun(run, 0, textRenderer.text().length());
442 if (!ellipsisRect.isEmpty()) {
443 if (textRenderer.style().isHorizontalWritingMode())
444 boundaries.setWidth(ellipsisRect.maxX() - boundaries.x());
445 else
446 boundaries.setHeight(ellipsisRect.maxY() - boundaries.y());
447 }
448 }
449
450 quads.append(textRenderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed));
451 }
452 return quads;
453}
454
455Vector<FloatQuad> RenderText::absoluteQuadsClippedToEllipsis() const
456{
457 return collectAbsoluteQuads(*this, nullptr, ClippingOption::ClipToEllipsis);
458}
459
460void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const
461{
462 quads.appendVector(collectAbsoluteQuads(*this, wasFixed, ClippingOption::NoClipping));
463}
464
465static FloatRect localQuadForTextRun(const InlineIterator::TextBox& run, unsigned start, unsigned end, bool useSelectionHeight)
466{
467 unsigned realEnd = std::min(run.end(), end);
468 LayoutRect boxSelectionRect = run.selectionRect(start, realEnd);
469 if (!boxSelectionRect.height())
470 return { };
471 if (useSelectionHeight)
472 return boxSelectionRect;
473
474 auto rect = run.visualRectIgnoringBlockDirection();
475 if (run.isHorizontal()) {
476 boxSelectionRect.setHeight(rect.height());
477 boxSelectionRect.setY(rect.y());
478 } else {
479 boxSelectionRect.setWidth(rect.width());
480 boxSelectionRect.setX(rect.x());
481 }
482 return boxSelectionRect;
483}
484
485Vector<FloatQuad> RenderText::absoluteQuadsForRange(unsigned start, unsigned end, bool useSelectionHeight, bool ignoreEmptyTextSelections, bool* wasFixed) const
486{
487 // Work around signed/unsigned issues. This function takes unsigneds, and is often passed UINT_MAX
488 // to mean "all the way to the end". LegacyInlineTextBox coordinates are unsigneds, so changing this
489 // function to take ints causes various internal mismatches. But selectionRect takes ints, and
490 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect to take unsigneds, but
491 // that would cause many ripple effects, so for now we'll just clamp our unsigned parameters to INT_MAX.
492 ASSERT(end == UINT_MAX || end <= INT_MAX);
493 ASSERT(start <= INT_MAX);
494 start = std::min(start, static_cast<unsigned>(INT_MAX));
495 end = std::min(end, static_cast<unsigned>(INT_MAX));
496
497 Vector<FloatQuad> quads;
498 for (auto& run : InlineIterator::textBoxesFor(*this)) {
499 if (ignoreEmptyTextSelections && !run.selectableRange().intersects(start, end))
500 continue;
501 if (start <= run.start() && run.end() <= end) {
502 auto boundaries = boundariesForTextRun(run);
503
504 if (useSelectionHeight) {
505 LayoutRect selectionRect = run.selectionRect(start, end);
506 if (run.isHorizontal()) {
507 boundaries.setHeight(selectionRect.height());
508 boundaries.setY(selectionRect.y());
509 } else {
510 boundaries.setWidth(selectionRect.width());
511 boundaries.setX(selectionRect.x());
512 }
513 }
514 quads.append(localToAbsoluteQuad(boundaries, UseTransforms, wasFixed));
515 continue;
516 }
517 FloatRect rect = localQuadForTextRun(run, start, end, useSelectionHeight);
518 if (!rect.isZero())
519 quads.append(localToAbsoluteQuad(rect, UseTransforms, wasFixed));
520 }
521 return quads;
522}
523
524Position RenderText::positionForPoint(const LayoutPoint& point)
525{
526 return positionForPoint(point, nullptr).deepEquivalent();
527}
528
529enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart };
530
531static bool lineDirectionPointFitsInBox(int pointLineDirection, const InlineIterator::TextBoxIterator& textRun, ShouldAffinityBeDownstream& shouldAffinityBeDownstream)
532{
533 shouldAffinityBeDownstream = AlwaysDownstream;
534
535 // the x coordinate is equal to the left edge of this box
536 // the affinity must be downstream so the position doesn't jump back to the previous line
537 // except when box is the first box in the line
538 if (pointLineDirection <= textRun->logicalLeft()) {
539 shouldAffinityBeDownstream = !textRun->previousOnLine() ? UpstreamIfPositionIsNotAtStart : AlwaysDownstream;
540 return true;
541 }
542
543#if !PLATFORM(IOS_FAMILY)
544 // and the x coordinate is to the left of the right edge of this box
545 // check to see if position goes in this box
546 if (pointLineDirection < textRun->logicalRight()) {
547 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
548 return true;
549 }
550#endif
551
552 // box is first on line
553 // and the x coordinate is to the left of the first text box left edge
554 if (!textRun->previousOnLineIgnoringLineBreak() && pointLineDirection < textRun->logicalLeft())
555 return true;
556
557 if (!textRun->nextOnLineIgnoringLineBreak()) {
558 // box is last on line
559 // and the x coordinate is to the right of the last text box right edge
560 // generate VisiblePosition, use Affinity::Upstream affinity if possible
561 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
562 return true;
563 }
564
565 return false;
566}
567
568static VisiblePosition createVisiblePositionForBox(const InlineIterator::BoxIterator& run, unsigned offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
569{
570 auto affinity = VisiblePosition::defaultAffinity;
571 switch (shouldAffinityBeDownstream) {
572 case AlwaysDownstream:
573 affinity = Affinity::Downstream;
574 break;
575 case AlwaysUpstream:
576 affinity = Affinity::Upstream;
577 break;
578 case UpstreamIfPositionIsNotAtStart:
579 affinity = offset > run->minimumCaretOffset() ? Affinity::Upstream : Affinity::Downstream;
580 break;
581 }
582 return run->renderer().createVisiblePosition(offset, affinity);
583}
584
585static VisiblePosition createVisiblePositionAfterAdjustingOffsetForBiDi(const InlineIterator::TextBoxIterator& run, unsigned offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
586{
587 if (offset && offset < run->length())
588 return createVisiblePositionForBox(run, run->start() + offset, shouldAffinityBeDownstream);
589
590 bool positionIsAtStartOfBox = !offset;
591 if (positionIsAtStartOfBox == run->isLeftToRightDirection()) {
592 // offset is on the left edge
593
594 auto previousRun = run->previousOnLineIgnoringLineBreak();
595 if ((previousRun && previousRun->bidiLevel() == run->bidiLevel())
596 || run->renderer().containingBlock()->style().direction() == run->direction()) // FIXME: left on 12CBA
597 return createVisiblePositionForBox(run, run->leftmostCaretOffset(), shouldAffinityBeDownstream);
598
599 if (previousRun && previousRun->bidiLevel() > run->bidiLevel()) {
600 // e.g. left of B in aDC12BAb
601 auto leftmostRun = previousRun;
602 for (; previousRun; previousRun.traversePreviousOnLineIgnoringLineBreak()) {
603 if (previousRun->bidiLevel() <= run->bidiLevel())
604 break;
605 leftmostRun = previousRun;
606 }
607 return createVisiblePositionForBox(leftmostRun, leftmostRun->rightmostCaretOffset(), shouldAffinityBeDownstream);
608 }
609
610 if (!previousRun || previousRun->bidiLevel() < run->bidiLevel()) {
611 // e.g. left of D in aDC12BAb
612 InlineIterator::BoxIterator rightmostRun = run;
613 for (auto nextRun = run->nextOnLineIgnoringLineBreak(); nextRun; nextRun.traverseNextOnLineIgnoringLineBreak()) {
614 if (nextRun->bidiLevel() < run->bidiLevel())
615 break;
616 rightmostRun = nextRun;
617 }
618 return createVisiblePositionForBox(rightmostRun,
619 run->isLeftToRightDirection() ? rightmostRun->maximumCaretOffset() : rightmostRun->minimumCaretOffset(), shouldAffinityBeDownstream);
620 }
621
622 return createVisiblePositionForBox(run, run->rightmostCaretOffset(), shouldAffinityBeDownstream);
623 }
624
625 auto nextRun = run->nextOnLineIgnoringLineBreak();
626 if ((nextRun && nextRun->bidiLevel() == run->bidiLevel())
627 || run->renderer().containingBlock()->style().direction() == run->direction())
628 return createVisiblePositionForBox(run, run->rightmostCaretOffset(), shouldAffinityBeDownstream);
629
630 // offset is on the right edge
631 if (nextRun && nextRun->bidiLevel() > run->bidiLevel()) {
632 // e.g. right of C in aDC12BAb
633 auto rightmostRun = nextRun;
634 for (; nextRun; nextRun.traverseNextOnLineIgnoringLineBreak()) {
635 if (nextRun->bidiLevel() <= run->bidiLevel())
636 break;
637 rightmostRun = nextRun;
638 }
639
640 return createVisiblePositionForBox(rightmostRun, rightmostRun->leftmostCaretOffset(), shouldAffinityBeDownstream);
641 }
642
643 if (!nextRun || nextRun->bidiLevel() < run->bidiLevel()) {
644 // e.g. right of A in aDC12BAb
645 InlineIterator::BoxIterator leftmostRun = run;
646 for (auto previousRun = run->previousOnLineIgnoringLineBreak(); previousRun; previousRun.traversePreviousOnLineIgnoringLineBreak()) {
647 if (previousRun->bidiLevel() < run->bidiLevel())
648 break;
649 leftmostRun = previousRun;
650 }
651
652 return createVisiblePositionForBox(leftmostRun,
653 run->isLeftToRightDirection() ? leftmostRun->minimumCaretOffset() : leftmostRun->maximumCaretOffset(), shouldAffinityBeDownstream);
654 }
655
656 return createVisiblePositionForBox(run, run->leftmostCaretOffset(), shouldAffinityBeDownstream);
657}
658
659
660VisiblePosition RenderText::positionForPoint(const LayoutPoint& point, const RenderFragmentContainer*)
661{
662 auto firstRun = InlineIterator::firstTextBoxFor(*this);
663
664 if (!firstRun || !text().length())
665 return createVisiblePosition(0, Affinity::Downstream);
666
667 LayoutUnit pointLineDirection = firstRun->isHorizontal() ? point.x() : point.y();
668 LayoutUnit pointBlockDirection = firstRun->isHorizontal() ? point.y() : point.x();
669 bool blocksAreFlipped = style().isFlippedBlocksWritingMode();
670
671 InlineIterator::TextBoxIterator lastRun;
672 for (auto run = firstRun; run; run.traverseNextTextBox()) {
673 if (run->isLineBreak() && !run->previousOnLine() && run->nextOnLine() && !run->nextOnLine()->isLineBreak())
674 run.traverseNextTextBox();
675
676 auto lineBox = run->lineBox();
677 auto top = LayoutUnit { std::min(previousLineBoxContentBottomOrBorderAndPadding(*lineBox), lineBox->contentLogicalTop()) };
678 if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirection == top)) {
679 auto bottom = LineSelection::logicalBottom(*lineBox);
680 if (auto nextLineBox = lineBox->next())
681 bottom = std::min(bottom, nextLineBox->contentLogicalTop());
682
683 if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockDirection == bottom)) {
684 ShouldAffinityBeDownstream shouldAffinityBeDownstream;
685#if PLATFORM(IOS_FAMILY)
686 if (pointLineDirection != run->logicalLeft() && point.x() < run->visualRectIgnoringBlockDirection().x() + run->logicalWidth()) {
687 int half = run->visualRectIgnoringBlockDirection().x() + run->logicalWidth() / 2;
688 auto affinity = point.x() < half ? Affinity::Downstream : Affinity::Upstream;
689 return createVisiblePosition(run->offsetForPosition(pointLineDirection) + run->start(), affinity);
690 }
691#endif
692 if (lineDirectionPointFitsInBox(pointLineDirection, run, shouldAffinityBeDownstream))
693 return createVisiblePositionAfterAdjustingOffsetForBiDi(run, run->offsetForPosition(pointLineDirection), shouldAffinityBeDownstream);
694 }
695 }
696 lastRun = run;
697 }
698
699 if (lastRun) {
700 ShouldAffinityBeDownstream shouldAffinityBeDownstream;
701 lineDirectionPointFitsInBox(pointLineDirection, lastRun, shouldAffinityBeDownstream);
702 return createVisiblePositionAfterAdjustingOffsetForBiDi(lastRun, lastRun->offsetForPosition(pointLineDirection) + lastRun->start(), shouldAffinityBeDownstream);
703 }
704 return createVisiblePosition(0, Affinity::Downstream);
705}
706
707static inline std::optional<float> combineTextWidth(const RenderText& renderer, const FontCascade& fontCascade, const RenderStyle& style)
708{
709 if (!style.hasTextCombine() || !is<RenderCombineText>(renderer))
710 return { };
711 auto& combineTextRenderer = downcast<RenderCombineText>(renderer);
712 return combineTextRenderer.isCombined() ? std::make_optional(combineTextRenderer.combinedTextWidth(fontCascade)) : std::nullopt;
713}
714
715ALWAYS_INLINE float RenderText::widthFromCache(const FontCascade& fontCascade, unsigned start, unsigned length, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow, const RenderStyle& style) const
716{
717 if (auto width = combineTextWidth(*this, fontCascade, style))
718 return *width;
719
720 TextRun run = RenderBlock::constructTextRun(*this, start, length, style);
721 run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
722 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
723 run.setXPos(xPos);
724 return fontCascade.width(run, fallbackFonts, glyphOverflow);
725}
726
727ALWAYS_INLINE float RenderText::widthFromCacheConsideringPossibleTrailingSpace(const RenderStyle& style, const FontCascade& font, unsigned startIndex, unsigned wordLen, float xPos, bool currentCharacterIsSpace, WordTrailingSpace& wordTrailingSpace, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow) const
728{
729 return measureTextConsideringPossibleTrailingSpace(currentCharacterIsSpace, startIndex, wordLen, wordTrailingSpace, fallbackFonts, [&] (unsigned from, unsigned len) {
730 return widthFromCache(font, from, len, xPos, &fallbackFonts, &glyphOverflow, style);
731 });
732}
733
734inline bool isHangablePunctuationAtLineStart(UChar c)
735{
736 return U_GET_GC_MASK(c) & (U_GC_PS_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
737}
738
739inline bool isHangablePunctuationAtLineEnd(UChar c)
740{
741 return U_GET_GC_MASK(c) & (U_GC_PE_MASK | U_GC_PI_MASK | U_GC_PF_MASK);
742}
743
744float RenderText::hangablePunctuationStartWidth(unsigned index) const
745{
746 unsigned length = text().length();
747 if (index >= length)
748 return 0;
749
750 if (!isHangablePunctuationAtLineStart(text()[index]))
751 return 0;
752
753 auto& style = this->style();
754 return widthFromCache(style.fontCascade(), index, 1, 0, 0, 0, style);
755}
756
757float RenderText::hangablePunctuationEndWidth(unsigned index) const
758{
759 unsigned length = text().length();
760 if (index >= length)
761 return 0;
762
763 if (!isHangablePunctuationAtLineEnd(text()[index]))
764 return 0;
765
766 auto& style = this->style();
767 return widthFromCache(style.fontCascade(), index, 1, 0, 0, 0, style);
768}
769
770bool RenderText::isHangableStopOrComma(UChar c)
771{
772 return c == 0x002C || c == 0x002E || c == 0x060C || c == 0x06D4 || c == 0x3001
773 || c == 0x3002 || c == 0xFF0C || c == 0xFF0E || c == 0xFE50 || c == 0xFE51
774 || c == 0xFE52 || c == 0xFF61 || c == 0xFF64;
775}
776
777unsigned RenderText::firstCharacterIndexStrippingSpaces() const
778{
779 if (!style().collapseWhiteSpace())
780 return 0;
781
782 unsigned i = 0;
783 for (unsigned length = text().length() ; i < length; ++i) {
784 if (text()[i] != ' ' && (text()[i] != '\n' || style().preserveNewline()) && text()[i] != '\t')
785 break;
786 }
787 return i;
788}
789
790unsigned RenderText::lastCharacterIndexStrippingSpaces() const
791{
792 if (!text().length())
793 return 0;
794
795 if (!style().collapseWhiteSpace())
796 return text().length() - 1;
797
798 int i = text().length() - 1;
799 for ( ; i >= 0; --i) {
800 if (text()[i] != ' ' && (text()[i] != '\n' || style().preserveNewline()) && text()[i] != '\t')
801 break;
802 }
803 return i;
804}
805
806RenderText::Widths RenderText::trimmedPreferredWidths(float leadWidth, bool& stripFrontSpaces)
807{
808 auto& style = this->style();
809 bool collapseWhiteSpace = style.collapseWhiteSpace();
810
811 if (!collapseWhiteSpace)
812 stripFrontSpaces = false;
813
814 if (m_hasTab || preferredLogicalWidthsDirty())
815 computePreferredLogicalWidths(leadWidth);
816
817 Widths widths;
818
819 widths.beginWS = !stripFrontSpaces && m_hasBeginWS;
820 widths.endWS = m_hasEndWS;
821
822 unsigned length = this->length();
823
824 if (!length || (stripFrontSpaces && text().isAllSpecialCharacters<isHTMLSpace>()))
825 return widths;
826
827 widths.min = m_minWidth;
828 widths.max = m_maxWidth;
829
830 widths.beginMin = m_beginMinWidth;
831 widths.endMin = m_endMinWidth;
832
833 widths.hasBreakableChar = m_hasBreakableChar;
834 widths.hasBreak = m_hasBreak;
835 widths.endsWithBreak = m_hasBreak && text()[length - 1] == '\n';
836
837 if (text()[0] == ' ' || (text()[0] == '\n' && !style.preserveNewline()) || text()[0] == '\t') {
838 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
839 if (stripFrontSpaces)
840 widths.max -= font.width(RenderBlock::constructTextRun(&space, 1, style));
841 else
842 widths.max += font.wordSpacing();
843 }
844
845 stripFrontSpaces = collapseWhiteSpace && m_hasEndWS;
846
847 if (!style.autoWrap() || widths.min > widths.max)
848 widths.min = widths.max;
849
850 // Compute our max widths by scanning the string for newlines.
851 if (widths.hasBreak) {
852 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
853 bool firstLine = true;
854 widths.beginMax = widths.max;
855 widths.endMax = widths.max;
856 for (unsigned i = 0; i < length; i++) {
857 unsigned lineLength = 0;
858 while (i + lineLength < length && text()[i + lineLength] != '\n')
859 lineLength++;
860
861 if (lineLength) {
862 widths.endMax = widthFromCache(font, i, lineLength, leadWidth + widths.endMax, 0, 0, style);
863 if (firstLine) {
864 firstLine = false;
865 leadWidth = 0;
866 widths.beginMax = widths.endMax;
867 }
868 i += lineLength;
869 } else if (firstLine) {
870 widths.beginMax = 0;
871 firstLine = false;
872 leadWidth = 0;
873 }
874
875 if (i == length - 1) {
876 // A <pre> run that ends with a newline, as in, e.g.,
877 // <pre>Some text\n\n<span>More text</pre>
878 widths.endMax = 0;
879 }
880 }
881 }
882
883 return widths;
884}
885
886static inline bool isSpaceAccordingToStyle(UChar c, const RenderStyle& style)
887{
888 return c == ' ' || (c == noBreakSpace && style.nbspMode() == NBSPMode::Space);
889}
890
891float RenderText::minLogicalWidth() const
892{
893 if (preferredLogicalWidthsDirty())
894 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
895
896 return m_minWidth;
897}
898
899float RenderText::maxLogicalWidth() const
900{
901 if (preferredLogicalWidthsDirty())
902 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0);
903
904 return m_maxWidth;
905}
906
907LineBreakIteratorMode mapLineBreakToIteratorMode(LineBreak lineBreak)
908{
909 switch (lineBreak) {
910 case LineBreak::Auto:
911 case LineBreak::AfterWhiteSpace:
912 case LineBreak::Anywhere:
913 return LineBreakIteratorMode::Default;
914 case LineBreak::Loose:
915 return LineBreakIteratorMode::Loose;
916 case LineBreak::Normal:
917 return LineBreakIteratorMode::Normal;
918 case LineBreak::Strict:
919 return LineBreakIteratorMode::Strict;
920 }
921 ASSERT_NOT_REACHED();
922 return LineBreakIteratorMode::Default;
923}
924
925void RenderText::computePreferredLogicalWidths(float leadWidth)
926{
927 HashSet<const Font*> fallbackFonts;
928 GlyphOverflow glyphOverflow;
929 computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow);
930 if (fallbackFonts.isEmpty() && !glyphOverflow.left && !glyphOverflow.right && !glyphOverflow.top && !glyphOverflow.bottom)
931 m_knownToHaveNoOverflowAndNoFallbackFonts = true;
932}
933
934static inline float hyphenWidth(RenderText& renderer, const FontCascade& font)
935{
936 const RenderStyle& style = renderer.style();
937 auto textRun = RenderBlock::constructTextRun(style.hyphenString().string(), style);
938 return font.width(textRun);
939}
940
941float RenderText::maxWordFragmentWidth(const RenderStyle& style, const FontCascade& font, StringView word, unsigned minimumPrefixLength, unsigned minimumSuffixLength, bool currentCharacterIsSpace, unsigned characterIndex, float xPos, float entireWordWidth, WordTrailingSpace& wordTrailingSpace, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
942{
943 unsigned suffixStart = 0;
944 if (word.length() <= minimumSuffixLength)
945 return entireWordWidth;
946
947 Vector<int, 8> hyphenLocations;
948 ASSERT(word.length() >= minimumSuffixLength);
949 unsigned hyphenLocation = word.length() - minimumSuffixLength;
950 while ((hyphenLocation = lastHyphenLocation(word, hyphenLocation, style.computedLocale())) >= std::max(minimumPrefixLength, 1U))
951 hyphenLocations.append(hyphenLocation);
952
953 if (hyphenLocations.isEmpty())
954 return entireWordWidth;
955
956 hyphenLocations.reverse();
957
958 // Consider the word "ABC-DEF-GHI" (where the '-' characters are hyphenation opportunities). We want to measure the width
959 // of "ABC-" and "DEF-", but not "GHI-". Instead, we should measure "GHI" the same way we measure regular unhyphenated
960 // words (by using wordTrailingSpace). Therefore, this function is split up into two parts - one that measures each prefix,
961 // and one that measures the single last suffix.
962
963 // FIXME: Breaking the string at these places in the middle of words doesn't work with complex text.
964 float minimumFragmentWidthToConsider = font.pixelSize() * 5 / 4 + hyphenWidth(*this, font);
965 float maxFragmentWidth = 0;
966 for (size_t k = 0; k < hyphenLocations.size(); ++k) {
967 int fragmentLength = hyphenLocations[k] - suffixStart;
968 StringBuilder fragmentWithHyphen;
969 fragmentWithHyphen.append(word.substring(suffixStart, fragmentLength));
970 fragmentWithHyphen.append(style.hyphenString());
971
972 TextRun run = RenderBlock::constructTextRun(fragmentWithHyphen.toString(), style);
973 run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
974 float fragmentWidth = font.width(run, &fallbackFonts, &glyphOverflow);
975
976 // Narrow prefixes are ignored. See tryHyphenating in RenderBlockLineLayout.cpp.
977 if (fragmentWidth <= minimumFragmentWidthToConsider)
978 continue;
979
980 suffixStart += fragmentLength;
981 maxFragmentWidth = std::max(maxFragmentWidth, fragmentWidth);
982 }
983
984 if (!suffixStart) {
985 // We didn't find any hyphenation opportunities that we're willing to actually use.
986 // Therefore, the width of the maximum fragment is just ... the width of the entire word.
987 return entireWordWidth;
988 }
989
990 auto suffixWidth = widthFromCacheConsideringPossibleTrailingSpace(style, font, characterIndex + suffixStart, word.length() - suffixStart, xPos, currentCharacterIsSpace, wordTrailingSpace, fallbackFonts, glyphOverflow);
991 return std::max(maxFragmentWidth, suffixWidth);
992}
993
994void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow)
995{
996 ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts);
997
998 m_minWidth = 0;
999 m_beginMinWidth = 0;
1000 m_endMinWidth = 0;
1001 m_maxWidth = 0;
1002
1003 float currMaxWidth = 0;
1004 m_hasBreakableChar = false;
1005 m_hasBreak = false;
1006 m_hasTab = false;
1007 m_hasBeginWS = false;
1008 m_hasEndWS = false;
1009
1010 auto& style = this->style();
1011 auto& font = style.fontCascade(); // FIXME: This ignores first-line.
1012 float wordSpacing = font.wordSpacing();
1013 auto& string = text();
1014 unsigned length = string.length();
1015 auto iteratorMode = mapLineBreakToIteratorMode(style.lineBreak());
1016 LazyLineBreakIterator breakIterator(string, style.computedLocale(), iteratorMode);
1017 bool needsWordSpacing = false;
1018 bool ignoringSpaces = false;
1019 bool isSpace = false;
1020 bool firstWord = true;
1021 bool firstLine = true;
1022 std::optional<unsigned> nextBreakable;
1023 unsigned lastWordBoundary = 0;
1024
1025 WordTrailingSpace wordTrailingSpace(style);
1026 // If automatic hyphenation is allowed, we keep track of the width of the widest word (or word
1027 // fragment) encountered so far, and only try hyphenating words that are wider.
1028 float maxWordWidth = std::numeric_limits<float>::max();
1029 unsigned minimumPrefixLength = 0;
1030 unsigned minimumSuffixLength = 0;
1031 if (style.hyphens() == Hyphens::Auto && canHyphenate(style.computedLocale())) {
1032 maxWordWidth = 0;
1033
1034 // Map 'hyphenate-limit-{before,after}: auto;' to 2.
1035 auto before = style.hyphenationLimitBefore();
1036 minimumPrefixLength = before < 0 ? 2 : before;
1037
1038 auto after = style.hyphenationLimitAfter();
1039 minimumSuffixLength = after < 0 ? 2 : after;
1040 }
1041
1042 std::optional<LayoutUnit> firstGlyphLeftOverflow;
1043
1044 bool breakNBSP = style.autoWrap() && style.nbspMode() == NBSPMode::Space;
1045
1046 bool breakAnywhere = style.lineBreak() == LineBreak::Anywhere && style.autoWrap();
1047 // Note the deliberate omission of word-wrap/overflow-wrap's break-word value from this breakAll check.
1048 // Those do not affect minimum preferred sizes. Note that break-word is a non-standard value for
1049 // word-break, but we support it as though it means break-all.
1050 bool breakAll = (style.wordBreak() == WordBreak::BreakAll || style.wordBreak() == WordBreak::BreakWord || style.overflowWrap() == OverflowWrap::Anywhere) && style.autoWrap();
1051 bool keepAllWords = style.wordBreak() == WordBreak::KeepAll;
1052 bool canUseLineBreakShortcut = iteratorMode == LineBreakIteratorMode::Default;
1053
1054 for (unsigned i = 0; i < length; i++) {
1055 UChar c = string[i];
1056
1057 bool previousCharacterIsSpace = isSpace;
1058
1059 bool isNewline = false;
1060 if (c == '\n') {
1061 if (style.preserveNewline()) {
1062 m_hasBreak = true;
1063 isNewline = true;
1064 isSpace = false;
1065 } else
1066 isSpace = true;
1067 } else if (c == '\t') {
1068 if (!style.collapseWhiteSpace()) {
1069 m_hasTab = true;
1070 isSpace = false;
1071 } else
1072 isSpace = true;
1073 } else
1074 isSpace = c == ' ';
1075
1076 if ((isSpace || isNewline) && !i)
1077 m_hasBeginWS = true;
1078 if ((isSpace || isNewline) && i == length - 1)
1079 m_hasEndWS = true;
1080
1081 ignoringSpaces |= style.collapseWhiteSpace() && previousCharacterIsSpace && isSpace;
1082 ignoringSpaces &= isSpace;
1083
1084 // Ignore spaces and soft hyphens
1085 if (ignoringSpaces) {
1086 ASSERT(lastWordBoundary == i);
1087 lastWordBoundary++;
1088 continue;
1089 } else if (c == softHyphen && style.hyphens() != Hyphens::None) {
1090 ASSERT(i >= lastWordBoundary);
1091 currMaxWidth += widthFromCache(font, lastWordBoundary, i - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
1092 if (!firstGlyphLeftOverflow)
1093 firstGlyphLeftOverflow = glyphOverflow.left;
1094 lastWordBoundary = i + 1;
1095 continue;
1096 }
1097
1098 bool hasBreak = breakAll || isBreakable(breakIterator, i, nextBreakable, breakNBSP, canUseLineBreakShortcut, keepAllWords, breakAnywhere);
1099 bool betweenWords = true;
1100 unsigned j = i;
1101 while (c != '\n' && !isSpaceAccordingToStyle(c, style) && c != '\t' && (c != softHyphen || style.hyphens() == Hyphens::None)) {
1102 UChar previousCharacter = c;
1103 j++;
1104 if (j == length)
1105 break;
1106 c = string[j];
1107 if (U_IS_LEAD(previousCharacter) && U_IS_TRAIL(c))
1108 continue;
1109 if (isBreakable(breakIterator, j, nextBreakable, breakNBSP, canUseLineBreakShortcut, keepAllWords, breakAnywhere) && characterAt(j - 1) != softHyphen)
1110 break;
1111 if (breakAll) {
1112 // FIXME: This code is ultra wrong.
1113 // The spec says "word-break: break-all: Any typographic letter units are treated as ID(“ideographic characters”) for the purpose of line-breaking."
1114 // The spec describes how a "typographic letter unit" is a cluster, not a code point: https://drafts.csswg.org/css-text-3/#typographic-character-unit
1115 betweenWords = false;
1116 break;
1117 }
1118 }
1119
1120 unsigned wordLen = j - i;
1121 if (wordLen) {
1122 float currMinWidth = 0;
1123 bool isSpace = (j < length) && isSpaceAccordingToStyle(c, style);
1124 float w = widthFromCacheConsideringPossibleTrailingSpace(style, font, i, wordLen, leadWidth + currMaxWidth, isSpace, wordTrailingSpace, fallbackFonts, glyphOverflow);
1125 if (c == softHyphen && style.hyphens() != Hyphens::None)
1126 currMinWidth = hyphenWidth(*this, font);
1127
1128 if (w > maxWordWidth) {
1129 auto maxFragmentWidth = maxWordFragmentWidth(style, font, StringView(string).substring(i, wordLen), minimumPrefixLength, minimumSuffixLength, isSpace, i, leadWidth + currMaxWidth, w, wordTrailingSpace, fallbackFonts, glyphOverflow);
1130 currMinWidth += maxFragmentWidth - w; // This, when combined with "currMinWidth += w" below, has the effect of executing "currMinWidth += maxFragmentWidth" instead.
1131 maxWordWidth = std::max(maxWordWidth, maxFragmentWidth);
1132 }
1133
1134 if (!firstGlyphLeftOverflow)
1135 firstGlyphLeftOverflow = glyphOverflow.left;
1136 currMinWidth += w;
1137 if (betweenWords) {
1138 if (lastWordBoundary == i)
1139 currMaxWidth += w;
1140 else {
1141 ASSERT(j >= lastWordBoundary);
1142 currMaxWidth += widthFromCache(font, lastWordBoundary, j - lastWordBoundary, leadWidth + currMaxWidth, &fallbackFonts, &glyphOverflow, style);
1143 }
1144 lastWordBoundary = j;
1145 }
1146
1147 bool isCollapsibleWhiteSpace = (j < length) && style.isCollapsibleWhiteSpace(c);
1148 if (j < length && style.autoWrap())
1149 m_hasBreakableChar = true;
1150
1151 // Add in wordSpacing to our currMaxWidth, but not if this is the last word on a line or the
1152 // last word in the run.
1153 if ((isSpace || isCollapsibleWhiteSpace) && !containsOnlyHTMLWhitespace(j, length - j))
1154 currMaxWidth += wordSpacing;
1155
1156 if (firstWord) {
1157 firstWord = false;
1158 // If the first character in the run is breakable, then we consider ourselves to have a beginning
1159 // minimum width of 0, since a break could occur right before our run starts, preventing us from ever
1160 // being appended to a previous text run when considering the total minimum width of the containing block.
1161 if (hasBreak)
1162 m_hasBreakableChar = true;
1163 m_beginMinWidth = hasBreak ? 0 : currMinWidth;
1164 }
1165 m_endMinWidth = currMinWidth;
1166
1167 m_minWidth = std::max(currMinWidth, m_minWidth);
1168
1169 i += wordLen - 1;
1170 } else {
1171 // Nowrap can never be broken, so don't bother setting the
1172 // breakable character boolean. Pre can only be broken if we encounter a newline.
1173 if (style.autoWrap() || isNewline)
1174 m_hasBreakableChar = true;
1175
1176 if (isNewline) { // Only set if preserveNewline was true and we saw a newline.
1177 if (firstLine) {
1178 firstLine = false;
1179 leadWidth = 0;
1180 if (!style.autoWrap())
1181 m_beginMinWidth = currMaxWidth;
1182 }
1183
1184 if (currMaxWidth > m_maxWidth)
1185 m_maxWidth = currMaxWidth;
1186 currMaxWidth = 0;
1187 } else {
1188 TextRun run = RenderBlock::constructTextRun(*this, i, 1, style);
1189 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1190 run.setXPos(leadWidth + currMaxWidth);
1191
1192 currMaxWidth += font.width(run, &fallbackFonts);
1193 glyphOverflow.right = 0;
1194 needsWordSpacing = isSpace && !previousCharacterIsSpace && i == length - 1;
1195 }
1196 ASSERT(lastWordBoundary == i);
1197 lastWordBoundary++;
1198 }
1199 }
1200
1201 glyphOverflow.left = firstGlyphLeftOverflow.value_or(glyphOverflow.left);
1202
1203 if ((needsWordSpacing && length > 1) || (ignoringSpaces && !firstWord))
1204 currMaxWidth += wordSpacing;
1205
1206 m_maxWidth = std::max(currMaxWidth, m_maxWidth);
1207
1208 if (!style.autoWrap())
1209 m_minWidth = m_maxWidth;
1210
1211 if (style.whiteSpace() == WhiteSpace::Pre) {
1212 if (firstLine)
1213 m_beginMinWidth = m_maxWidth;
1214 m_endMinWidth = currMaxWidth;
1215 }
1216
1217 setPreferredLogicalWidthsDirty(false);
1218}
1219
1220template<typename CharacterType> static inline bool isAllCollapsibleWhitespace(const CharacterType* characters, unsigned length, const RenderStyle& style)
1221{
1222 for (unsigned i = 0; i < length; ++i) {
1223 if (!style.isCollapsibleWhiteSpace(characters[i]))
1224 return false;
1225 }
1226 return true;
1227}
1228
1229bool RenderText::isAllCollapsibleWhitespace() const
1230{
1231 if (text().is8Bit())
1232 return WebCore::isAllCollapsibleWhitespace(text().characters8(), text().length(), style());
1233 return WebCore::isAllCollapsibleWhitespace(text().characters16(), text().length(), style());
1234}
1235
1236template<typename CharacterType> static inline bool isAllPossiblyCollapsibleWhitespace(const CharacterType* characters, unsigned length)
1237{
1238 for (unsigned i = 0; i < length; ++i) {
1239 if (!(characters[i] == '\n' || characters[i] == ' ' || characters[i] == '\t'))
1240 return false;
1241 }
1242 return true;
1243}
1244
1245bool RenderText::containsOnlyHTMLWhitespace(unsigned from, unsigned length) const
1246{
1247 ASSERT(from <= text().length());
1248 ASSERT(length <= text().length());
1249 ASSERT(from + length <= text().length());
1250 if (text().is8Bit())
1251 return isAllPossiblyCollapsibleWhitespace(text().characters8() + from, length);
1252 return isAllPossiblyCollapsibleWhitespace(text().characters16() + from, length);
1253}
1254
1255Vector<std::pair<unsigned, unsigned>> RenderText::draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const
1256{
1257 if (!textNode())
1258 return { };
1259
1260 auto markers = document().markers().markersFor(*textNode(), DocumentMarker::DraggedContent);
1261 if (markers.isEmpty())
1262 return { };
1263
1264 Vector<std::pair<unsigned, unsigned>> draggedContentRanges;
1265 for (auto* marker : markers) {
1266 unsigned markerStart = std::max(marker->startOffset(), startOffset);
1267 unsigned markerEnd = std::min(marker->endOffset(), endOffset);
1268 if (markerStart >= markerEnd || markerStart > endOffset || markerEnd < startOffset)
1269 continue;
1270
1271 std::pair<unsigned, unsigned> draggedContentRange;
1272 draggedContentRange.first = markerStart;
1273 draggedContentRange.second = markerEnd;
1274 draggedContentRanges.append(draggedContentRange);
1275 }
1276 return draggedContentRanges;
1277}
1278
1279IntPoint RenderText::firstRunLocation() const
1280{
1281 auto first = InlineIterator::firstTextBoxFor(*this);
1282 if (!first)
1283 return { };
1284 return IntPoint(first->visualRectIgnoringBlockDirection().location());
1285}
1286
1287void RenderText::setSelectionState(HighlightState state)
1288{
1289 RenderObject::setSelectionState(state);
1290
1291 // The containing block can be null in case of an orphaned tree.
1292 RenderBlock* containingBlock = this->containingBlock();
1293 if (containingBlock && !containingBlock->isRenderView())
1294 containingBlock->setSelectionState(state);
1295}
1296
1297void RenderText::setTextWithOffset(const String& newText, unsigned offset, unsigned length, bool force)
1298{
1299 if (!force && text() == newText)
1300 return;
1301
1302 int delta = newText.length() - text().length();
1303 unsigned end = offset + length;
1304
1305 m_linesDirty = m_lineBoxes.dirtyRange(*this, offset, end, delta);
1306
1307 setText(newText, force || m_linesDirty);
1308}
1309
1310static inline bool isInlineFlowOrEmptyText(const RenderObject& renderer)
1311{
1312 return is<RenderInline>(renderer) || (is<RenderText>(renderer) && downcast<RenderText>(renderer).text().isEmpty());
1313}
1314
1315UChar RenderText::previousCharacter() const
1316{
1317 // find previous text renderer if one exists
1318 const RenderObject* previousText = this;
1319 while ((previousText = previousText->previousInPreOrder())) {
1320 if (!isInlineFlowOrEmptyText(*previousText))
1321 break;
1322 }
1323 if (!is<RenderText>(previousText))
1324 return ' ';
1325 auto& previousString = downcast<RenderText>(*previousText).text();
1326 return previousString[previousString.length() - 1];
1327}
1328
1329String applyTextTransform(const RenderStyle& style, const String& text, UChar previousCharacter)
1330{
1331 switch (style.textTransform()) {
1332 case TextTransform::None:
1333 return text;
1334 case TextTransform::Capitalize:
1335 return capitalize(text, previousCharacter); // FIXME: Need to take locale into account.
1336 case TextTransform::Uppercase:
1337 return text.convertToUppercaseWithLocale(style.computedLocale());
1338 case TextTransform::Lowercase:
1339 return text.convertToLowercaseWithLocale(style.computedLocale());
1340 }
1341 ASSERT_NOT_REACHED();
1342 return text;
1343}
1344
1345void RenderText::setRenderedText(const String& newText)
1346{
1347 ASSERT(!newText.isNull());
1348
1349 String originalText = this->originalText();
1350
1351 m_text = newText;
1352
1353 if (m_useBackslashAsYenSymbol)
1354 m_text = makeStringByReplacingAll(m_text, '\\', yenSign);
1355
1356 const auto& style = this->style();
1357 if (style.textTransform() != TextTransform::None)
1358 m_text = applyTextTransform(style, m_text, previousCharacter());
1359
1360 // At rendering time, if certain fonts are used, these characters get swapped out with higher-quality PUA characters.
1361 // See RenderBlock::updateSecurityDiscCharacters().
1362 switch (style.textSecurity()) {
1363 case TextSecurity::None:
1364 break;
1365#if !PLATFORM(IOS_FAMILY)
1366 // We use the same characters here as for list markers.
1367 // See the listMarkerText function in RenderListMarker.cpp.
1368 case TextSecurity::Circle:
1369 secureText(whiteBullet);
1370 break;
1371 case TextSecurity::Disc:
1372 secureText(bullet);
1373 break;
1374 case TextSecurity::Square:
1375 secureText(blackSquare);
1376 break;
1377#else
1378 // FIXME: Why this quirk on iOS?
1379 case TextSecurity::Circle:
1380 case TextSecurity::Disc:
1381 case TextSecurity::Square:
1382 secureText(blackCircle);
1383 break;
1384#endif
1385 }
1386
1387 m_isAllASCII = text().isAllASCII();
1388 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath();
1389 m_canUseSimplifiedTextMeasuring = computeCanUseSimplifiedTextMeasuring();
1390
1391 if (m_text != originalText) {
1392 originalTextMap().set(this, originalText);
1393 m_originalTextDiffersFromRendered = true;
1394 } else if (m_originalTextDiffersFromRendered) {
1395 originalTextMap().remove(this);
1396 m_originalTextDiffersFromRendered = false;
1397 }
1398}
1399
1400void RenderText::secureText(UChar maskingCharacter)
1401{
1402 // This hides the text by replacing all the characters with the masking character.
1403 // Offsets within the hidden text have to match offsets within the original text
1404 // to handle things like carets and selection, so this won't work right if any
1405 // of the characters are surrogate pairs or combining marks. Thus, this function
1406 // does not attempt to handle either of those.
1407
1408 unsigned length = text().length();
1409 if (!length)
1410 return;
1411
1412 UChar characterToReveal = 0;
1413 unsigned revealedCharactersOffset = 0;
1414
1415 if (SecureTextTimer* timer = secureTextTimers().get(this)) {
1416 // We take the offset out of the timer to make this one-shot. We count on this being called only once.
1417 // If it's called a second time we assume the text is different and a character should not be revealed.
1418 revealedCharactersOffset = timer->takeOffsetAfterLastTypedCharacter();
1419 if (revealedCharactersOffset && revealedCharactersOffset <= length)
1420 characterToReveal = text()[--revealedCharactersOffset];
1421 }
1422
1423 UChar* characters;
1424 m_text = String::createUninitialized(length, characters);
1425
1426 for (unsigned i = 0; i < length; ++i)
1427 characters[i] = maskingCharacter;
1428 if (characterToReveal)
1429 characters[revealedCharactersOffset] = characterToReveal;
1430}
1431
1432bool RenderText::computeCanUseSimplifiedTextMeasuring() const
1433{
1434 if (!m_canUseSimpleFontCodePath)
1435 return false;
1436
1437 // FIXME: All these checks should be more fine-grained at the inline item level.
1438 auto& style = this->style();
1439 auto& fontCascade = style.fontCascade();
1440 if (fontCascade.wordSpacing() || fontCascade.letterSpacing())
1441 return false;
1442
1443 // Additional check on the font codepath.
1444 TextRun run(m_text);
1445 run.setCharacterScanForCodePath(false);
1446 if (fontCascade.codePath(run) != FontCascade::CodePath::Simple)
1447 return false;
1448
1449 if (&style != &firstLineStyle() && fontCascade != firstLineStyle().fontCascade())
1450 return false;
1451
1452 auto& primaryFont = fontCascade.primaryFont();
1453 if (primaryFont.syntheticBoldOffset())
1454 return false;
1455
1456 auto whitespaceIsCollapsed = style.collapseWhiteSpace();
1457 for (unsigned i = 0; i < text().length(); ++i) {
1458 auto character = text()[i];
1459 if (!WidthIterator::characterCanUseSimplifiedTextMeasuring(character, whitespaceIsCollapsed))
1460 return false;
1461 auto glyphData = fontCascade.glyphDataForCharacter(character, false);
1462 if (!glyphData.isValid() || glyphData.font != &primaryFont)
1463 return false;
1464 }
1465 return true;
1466}
1467
1468void RenderText::setText(const String& text, bool force)
1469{
1470 ASSERT(!text.isNull());
1471
1472 if (!force && text == originalText())
1473 return;
1474
1475 m_text = text;
1476 if (m_originalTextDiffersFromRendered) {
1477 originalTextMap().remove(this);
1478 m_originalTextDiffersFromRendered = false;
1479 }
1480
1481 setRenderedText(text);
1482
1483 setNeedsLayoutAndPrefWidthsRecalc();
1484 m_knownToHaveNoOverflowAndNoFallbackFonts = false;
1485
1486#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
1487 if (auto* container = LayoutIntegration::LineLayout::blockContainer(*this))
1488 container->invalidateLineLayoutPath();
1489#endif
1490
1491 if (AXObjectCache* cache = document().existingAXObjectCache())
1492 cache->deferTextChangedIfNeeded(textNode());
1493}
1494
1495String RenderText::textWithoutConvertingBackslashToYenSymbol() const
1496{
1497 if (!m_useBackslashAsYenSymbol || style().textSecurity() != TextSecurity::None)
1498 return text();
1499
1500 if (style().textTransform() == TextTransform::None)
1501 return originalText();
1502
1503 return applyTextTransform(style(), originalText(), previousCharacter());
1504}
1505
1506void RenderText::dirtyLineBoxes(bool fullLayout)
1507{
1508 if (fullLayout)
1509 deleteLineBoxes();
1510 else if (!m_linesDirty)
1511 m_lineBoxes.dirtyAll();
1512 m_linesDirty = false;
1513}
1514
1515void RenderText::deleteLineBoxes()
1516{
1517 m_lineBoxes.deleteAll();
1518}
1519
1520std::unique_ptr<LegacyInlineTextBox> RenderText::createTextBox()
1521{
1522 return makeUnique<LegacyInlineTextBox>(*this);
1523}
1524
1525void RenderText::positionLineBox(LegacyInlineTextBox& textBox)
1526{
1527 if (!textBox.hasTextContent())
1528 return;
1529 m_needsVisualReordering |= !textBox.isLeftToRightDirection();
1530}
1531
1532bool RenderText::usesLegacyLineLayoutPath() const
1533{
1534#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
1535 return !LayoutIntegration::LineLayout::containing(*this);
1536#else
1537 return true;
1538#endif
1539}
1540
1541float RenderText::width(unsigned from, unsigned len, float xPos, bool firstLine, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
1542{
1543 if (from >= text().length())
1544 return 0;
1545
1546 if (from + len > text().length())
1547 len = text().length() - from;
1548
1549 const RenderStyle& lineStyle = firstLine ? firstLineStyle() : style();
1550 return width(from, len, lineStyle.fontCascade(), xPos, fallbackFonts, glyphOverflow);
1551}
1552
1553float RenderText::width(unsigned from, unsigned length, const FontCascade& fontCascade, float xPos, HashSet<const Font*>* fallbackFonts, GlyphOverflow* glyphOverflow) const
1554{
1555 ASSERT(from + length <= text().length());
1556 if (!text().length() || !length)
1557 return 0.f;
1558
1559 auto& style = this->style();
1560 if (auto width = combineTextWidth(*this, fontCascade, style))
1561 return *width;
1562
1563 if (length == 1 && (characterAt(from) == space))
1564 return canUseSimplifiedTextMeasuring() ? fontCascade.primaryFont().spaceWidth() : fontCascade.widthOfSpaceString();
1565
1566 float width = 0.f;
1567 if (&fontCascade == &style.fontCascade()) {
1568 if (!style.preserveNewline() && !from && length == text().length() && (!glyphOverflow || !glyphOverflow->computeBounds)) {
1569 if (fallbackFonts) {
1570 ASSERT(glyphOverflow);
1571 if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAndNoFallbackFonts) {
1572 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0, *fallbackFonts, *glyphOverflow);
1573 if (fallbackFonts->isEmpty() && !glyphOverflow->left && !glyphOverflow->right && !glyphOverflow->top && !glyphOverflow->bottom)
1574 m_knownToHaveNoOverflowAndNoFallbackFonts = true;
1575 }
1576 width = m_maxWidth;
1577 } else
1578 width = maxLogicalWidth();
1579 } else
1580 width = widthFromCache(fontCascade, from, length, xPos, fallbackFonts, glyphOverflow, style);
1581 } else {
1582 TextRun run = RenderBlock::constructTextRun(*this, from, length, style);
1583 run.setCharacterScanForCodePath(!canUseSimpleFontCodePath());
1584 run.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
1585 run.setXPos(xPos);
1586
1587 width = fontCascade.width(run, fallbackFonts, glyphOverflow);
1588 }
1589
1590 return clampTo(width, 0.f);
1591}
1592
1593IntRect RenderText::linesBoundingBox() const
1594{
1595 auto firstTextBox = InlineIterator::firstTextBoxFor(*this);
1596 if (!firstTextBox)
1597 return { };
1598
1599 auto boundingBox = firstTextBox->visualRectIgnoringBlockDirection();
1600 for (auto textBox = firstTextBox; ++textBox;)
1601 boundingBox.uniteEvenIfEmpty(textBox->visualRectIgnoringBlockDirection());
1602
1603 return enclosingIntRect(boundingBox);
1604}
1605
1606LayoutRect RenderText::clippedOverflowRect(const RenderLayerModelObject* repaintContainer, VisibleRectContext context) const
1607{
1608 RenderObject* rendererToRepaint = containingBlock();
1609
1610 // Do not cross self-painting layer boundaries.
1611 RenderObject& enclosingLayerRenderer = enclosingLayer()->renderer();
1612 if (&enclosingLayerRenderer != rendererToRepaint && !rendererToRepaint->isDescendantOf(&enclosingLayerRenderer))
1613 rendererToRepaint = &enclosingLayerRenderer;
1614
1615 // The renderer we chose to repaint may be an ancestor of repaintContainer, but we need to do a repaintContainer-relative repaint.
1616 if (repaintContainer && repaintContainer != rendererToRepaint && !rendererToRepaint->isDescendantOf(repaintContainer))
1617 return repaintContainer->clippedOverflowRect(repaintContainer, context);
1618
1619 return rendererToRepaint->clippedOverflowRect(repaintContainer, context);
1620}
1621
1622LayoutRect RenderText::collectSelectionGeometriesForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<FloatQuad>* quads)
1623{
1624 ASSERT(!needsLayout());
1625
1626 if (selectionState() == HighlightState::None)
1627 return LayoutRect();
1628 if (!containingBlock())
1629 return LayoutRect();
1630
1631 // Now calculate startPos and endPos for painting selection.
1632 // We include a selection while endPos > 0
1633 unsigned startOffset;
1634 unsigned endOffset;
1635 if (selectionState() == HighlightState::Inside) {
1636 // We are fully selected.
1637 startOffset = 0;
1638 endOffset = text().length();
1639 } else {
1640 startOffset = view().selection().startOffset();
1641 endOffset = view().selection().endOffset();
1642 if (selectionState() == HighlightState::Start)
1643 endOffset = text().length();
1644 else if (selectionState() == HighlightState::End)
1645 startOffset = 0;
1646 }
1647
1648 if (startOffset == endOffset)
1649 return IntRect();
1650
1651 LayoutRect resultRect;
1652
1653 for (auto& run : InlineIterator::textBoxesFor(*this)) {
1654 LayoutRect rect;
1655 rect.unite(run.selectionRect(startOffset, endOffset));
1656 rect.unite(ellipsisRectForTextRun(run, startOffset, endOffset));
1657 if (rect.isEmpty())
1658 continue;
1659
1660 resultRect.unite(rect);
1661
1662 if (quads)
1663 quads->append(localToContainerQuad(FloatRect(rect), repaintContainer));
1664 }
1665
1666 if (clipToVisibleContent)
1667 return computeRectForRepaint(resultRect, repaintContainer);
1668 return localToContainerQuad(FloatRect(resultRect), repaintContainer).enclosingBoundingBox();
1669}
1670
1671LayoutRect RenderText::collectSelectionGeometriesForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<FloatQuad>& quads)
1672{
1673 return collectSelectionGeometriesForLineBoxes(repaintContainer, clipToVisibleContent, &quads);
1674}
1675
1676LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent)
1677{
1678 return collectSelectionGeometriesForLineBoxes(repaintContainer, clipToVisibleContent, nullptr);
1679}
1680
1681int RenderText::caretMinOffset() const
1682{
1683 auto first = InlineIterator::firstTextBoxFor(*this);
1684 if (!first)
1685 return 0;
1686
1687 int minOffset = first->start();
1688 for (auto box = first; ++box;)
1689 minOffset = std::min<int>(minOffset, box->start());
1690
1691 return minOffset;
1692}
1693
1694int RenderText::caretMaxOffset() const
1695{
1696 auto first = InlineIterator::firstTextBoxFor(*this);
1697 if (!first)
1698 return text().length();
1699
1700 int maxOffset = first->end();
1701 for (auto box = first; ++box;)
1702 maxOffset = std::max<int>(maxOffset, box->end());
1703
1704 return maxOffset;
1705}
1706
1707unsigned RenderText::countRenderedCharacterOffsetsUntil(unsigned offset) const
1708{
1709 unsigned result = 0;
1710 for (auto& run : InlineIterator::textBoxesFor(*this)) {
1711 auto start = run.start();
1712 auto length = run.length();
1713 if (offset < start)
1714 return result;
1715 if (offset <= start + length) {
1716 result += offset - start;
1717 return result;
1718 }
1719 result += length;
1720 }
1721 return result;
1722}
1723
1724enum class OffsetType { Character, Caret };
1725static bool containsOffset(const RenderText& text, unsigned offset, OffsetType type)
1726{
1727 for (auto [box, orderCache] = InlineIterator::firstTextBoxInLogicalOrderFor(text); box; box = InlineIterator::nextTextBoxInLogicalOrder(box, orderCache)) {
1728 auto start = box->start();
1729 if (offset < start)
1730 return false;
1731 unsigned end = box->end();
1732 if (offset >= start && offset <= end) {
1733 if (offset == end && (type == OffsetType::Character || box->isLineBreak()))
1734 continue;
1735 if (type == OffsetType::Character)
1736 return true;
1737 // Return false for offsets inside composed characters.
1738 return !offset || offset == static_cast<unsigned>(text.nextOffset(text.previousOffset(offset)));
1739 }
1740 }
1741 return false;
1742}
1743
1744bool RenderText::containsRenderedCharacterOffset(unsigned offset) const
1745{
1746 return containsOffset(*this, offset, OffsetType::Character);
1747}
1748
1749bool RenderText::containsCaretOffset(unsigned offset) const
1750{
1751 return containsOffset(*this, offset, OffsetType::Caret);
1752}
1753
1754bool RenderText::hasRenderedText() const
1755{
1756 for (auto& box : InlineIterator::textBoxesFor(*this)) {
1757 if (box.length())
1758 return true;
1759 }
1760 return false;
1761}
1762
1763int RenderText::previousOffset(int current) const
1764{
1765 if (m_isAllASCII || text().is8Bit())
1766 return current - 1;
1767
1768 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Caret, nullAtom());
1769 return iterator.preceding(current).value_or(current - 1);
1770}
1771
1772int RenderText::previousOffsetForBackwardDeletion(int current) const
1773{
1774 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Delete, nullAtom());
1775 return iterator.preceding(current).value_or(0);
1776}
1777
1778int RenderText::nextOffset(int current) const
1779{
1780 if (m_isAllASCII || text().is8Bit())
1781 return current + 1;
1782
1783 CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Caret, nullAtom());
1784 return iterator.following(current).value_or(current + 1);
1785}
1786
1787bool RenderText::computeCanUseSimpleFontCodePath() const
1788{
1789 if (m_isAllASCII || text().is8Bit())
1790 return true;
1791 return FontCascade::characterRangeCodePath(text().characters16(), length()) == FontCascade::CodePath::Simple;
1792}
1793
1794void RenderText::momentarilyRevealLastTypedCharacter(unsigned offsetAfterLastTypedCharacter)
1795{
1796 if (style().textSecurity() == TextSecurity::None)
1797 return;
1798 auto& secureTextTimer = secureTextTimers().add(this, nullptr).iterator->value;
1799 if (!secureTextTimer)
1800 secureTextTimer = makeUnique<SecureTextTimer>(*this);
1801 secureTextTimer->restart(offsetAfterLastTypedCharacter);
1802}
1803
1804StringView RenderText::stringView(unsigned start, std::optional<unsigned> stop) const
1805{
1806 unsigned destination = stop.value_or(text().length());
1807 ASSERT(start <= length());
1808 ASSERT(destination <= length());
1809 ASSERT(start <= destination);
1810 if (text().is8Bit())
1811 return { text().characters8() + start, destination - start };
1812 return { text().characters16() + start, destination - start };
1813}
1814
1815RenderInline* RenderText::inlineWrapperForDisplayContents()
1816{
1817 ASSERT(m_hasInlineWrapperForDisplayContents == inlineWrapperForDisplayContentsMap().contains(this));
1818
1819 if (!m_hasInlineWrapperForDisplayContents)
1820 return nullptr;
1821 return inlineWrapperForDisplayContentsMap().get(this).get();
1822}
1823
1824void RenderText::setInlineWrapperForDisplayContents(RenderInline* wrapper)
1825{
1826 ASSERT(m_hasInlineWrapperForDisplayContents == inlineWrapperForDisplayContentsMap().contains(this));
1827
1828 if (!wrapper) {
1829 if (!m_hasInlineWrapperForDisplayContents)
1830 return;
1831 inlineWrapperForDisplayContentsMap().remove(this);
1832 m_hasInlineWrapperForDisplayContents = false;
1833 return;
1834 }
1835 inlineWrapperForDisplayContentsMap().add(this, wrapper);
1836 m_hasInlineWrapperForDisplayContents = true;
1837}
1838
1839} // namespace WebCore
Note: See TracBrowser for help on using the repository browser.