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 | |
66 | namespace WebCore { |
67 | |
68 | using namespace WTF::Unicode; |
69 | |
70 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderText); |
71 | |
72 | struct 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 | |
82 | COMPILE_ASSERT(sizeof(RenderText) == sizeof(SameSizeAsRenderText), RenderText_should_stay_small); |
83 | |
84 | class SecureTextTimer final : private TimerBase { |
85 | WTF_MAKE_FAST_ALLOCATED; |
86 | public: |
87 | explicit SecureTextTimer(RenderText&); |
88 | void restart(unsigned offsetAfterLastTypedCharacter); |
89 | |
90 | unsigned takeOffsetAfterLastTypedCharacter(); |
91 | |
92 | private: |
93 | void fired() override; |
94 | RenderText& m_renderer; |
95 | unsigned m_offsetAfterLastTypedCharacter { 0 }; |
96 | }; |
97 | |
98 | typedef HashMap<RenderText*, std::unique_ptr<SecureTextTimer>> SecureTextTimerMap; |
99 | |
100 | static SecureTextTimerMap& secureTextTimers() |
101 | { |
102 | static NeverDestroyed<SecureTextTimerMap> map; |
103 | return map.get(); |
104 | } |
105 | |
106 | inline SecureTextTimer::SecureTextTimer(RenderText& renderer) |
107 | : m_renderer(renderer) |
108 | { |
109 | } |
110 | |
111 | inline void SecureTextTimer::restart(unsigned offsetAfterLastTypedCharacter) |
112 | { |
113 | m_offsetAfterLastTypedCharacter = offsetAfterLastTypedCharacter; |
114 | startOneShot(1_s * m_renderer.settings().passwordEchoDurationInSeconds()); |
115 | } |
116 | |
117 | inline unsigned SecureTextTimer::takeOffsetAfterLastTypedCharacter() |
118 | { |
119 | unsigned offset = m_offsetAfterLastTypedCharacter; |
120 | m_offsetAfterLastTypedCharacter = 0; |
121 | return offset; |
122 | } |
123 | |
124 | void 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 | |
131 | static HashMap<const RenderText*, String>& originalTextMap() |
132 | { |
133 | static NeverDestroyed<HashMap<const RenderText*, String>> map; |
134 | return map; |
135 | } |
136 | |
137 | static HashMap<const RenderText*, WeakPtr<RenderInline>>& inlineWrapperForDisplayContentsMap() |
138 | { |
139 | static NeverDestroyed<HashMap<const RenderText*, WeakPtr<RenderInline>>> map; |
140 | return map; |
141 | } |
142 | |
143 | static inline UChar convertNoBreakSpace(UChar character) |
144 | { |
145 | return character == noBreakSpace ? ' ' : character; |
146 | } |
147 | |
148 | String 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 | |
180 | inline 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 | |
207 | RenderText::RenderText(Text& textNode, const String& text) |
208 | : RenderText(static_cast<Node&>(textNode), text) |
209 | { |
210 | } |
211 | |
212 | RenderText::RenderText(Document& document, const String& text) |
213 | : RenderText(static_cast<Node&>(document), text) |
214 | { |
215 | } |
216 | |
217 | RenderText::~RenderText() |
218 | { |
219 | // Do not add any code here. Add it to willBeDestroyed() instead. |
220 | ASSERT(!originalTextMap().contains(this)); |
221 | } |
222 | |
223 | const char* RenderText::renderName() const |
224 | { |
225 | return "RenderText" ; |
226 | } |
227 | |
228 | Text* RenderText::textNode() const |
229 | { |
230 | return downcast<Text>(RenderObject::node()); |
231 | } |
232 | |
233 | bool RenderText::isTextFragment() const |
234 | { |
235 | return false; |
236 | } |
237 | |
238 | bool 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 | |
252 | void 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 | |
282 | void 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 | |
293 | void 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 | |
307 | void RenderText::deleteLineBoxesBeforeSimpleLineLayout() |
308 | { |
309 | m_lineBoxes.deleteAll(); |
310 | } |
311 | |
312 | String RenderText::originalText() const |
313 | { |
314 | return m_originalTextDiffersFromRendered ? originalTextMap().get(this) : m_text; |
315 | } |
316 | |
317 | void 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 | |
326 | Vector<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. |
347 | void 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 | |
420 | Vector<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 | |
429 | void 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 | |
438 | Vector<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 | |
455 | Position 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 | |
469 | VisiblePosition RenderText::positionForPoint(const LayoutPoint& point, const RenderFragmentContainer*) |
470 | { |
471 | ensureLineBoxes(); |
472 | return m_lineBoxes.positionForPoint(*this, point); |
473 | } |
474 | |
475 | LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, unsigned caretOffset, LayoutUnit* ) |
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 | |
485 | ALWAYS_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 | |
530 | inline 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 | |
535 | inline 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 | |
540 | float 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 | |
553 | float 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 | |
566 | bool 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 | |
573 | unsigned 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 | |
586 | unsigned 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 | |
602 | RenderText::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 | |
681 | static inline bool isSpaceAccordingToStyle(UChar c, const RenderStyle& style) |
682 | { |
683 | return c == ' ' || (c == noBreakSpace && style.nbspMode() == NBSPMode::Space); |
684 | } |
685 | |
686 | float RenderText::minLogicalWidth() const |
687 | { |
688 | if (preferredLogicalWidthsDirty()) |
689 | const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); |
690 | |
691 | return m_minWidth; |
692 | } |
693 | |
694 | float RenderText::maxLogicalWidth() const |
695 | { |
696 | if (preferredLogicalWidthsDirty()) |
697 | const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); |
698 | |
699 | return m_maxWidth; |
700 | } |
701 | |
702 | LineBreakIteratorMode 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 | |
720 | void 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 | |
729 | static 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 | |
736 | static float maxWordFragmentWidth(RenderText& renderer, const RenderStyle& style, const FontCascade& font, StringView word, unsigned minimumPrefixLength, unsigned minimumSuffixLength, unsigned& suffixStart, HashSet<const Font*>& fallbackFonts, GlyphOverflow& glyphOverflow) |
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 | |
777 | void 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 | |
1021 | template<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 | |
1030 | bool 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 | |
1037 | template<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 | |
1046 | bool 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 | |
1056 | Vector<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 | |
1080 | IntPoint RenderText::firstRunLocation() const |
1081 | { |
1082 | if (auto* layout = simpleLineLayout()) |
1083 | return SimpleLineLayout::computeFirstRunLocation(*this, *layout); |
1084 | |
1085 | return m_lineBoxes.firstRunLocation(); |
1086 | } |
1087 | |
1088 | void 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 | |
1104 | void 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 | |
1117 | static inline bool isInlineFlowOrEmptyText(const RenderObject& renderer) |
1118 | { |
1119 | return is<RenderInline>(renderer) || (is<RenderText>(renderer) && downcast<RenderText>(renderer).text().isEmpty()); |
1120 | } |
1121 | |
1122 | UChar 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 | |
1136 | LayoutUnit RenderText::topOfFirstText() const |
1137 | { |
1138 | return firstTextBox()->root().lineTop(); |
1139 | } |
1140 | |
1141 | String 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 | |
1157 | void 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 | |
1210 | void 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 | |
1242 | bool 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 | |
1265 | void 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 | |
1290 | String 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 | |
1301 | void 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 | |
1310 | std::unique_ptr<InlineTextBox> RenderText::createTextBox() |
1311 | { |
1312 | return std::make_unique<InlineTextBox>(*this); |
1313 | } |
1314 | |
1315 | void RenderText::positionLineBox(InlineTextBox& textBox) |
1316 | { |
1317 | if (!textBox.hasTextContent()) |
1318 | return; |
1319 | m_containsReversedText |= !textBox.isLeftToRightDirection(); |
1320 | } |
1321 | |
1322 | void RenderText::ensureLineBoxes() |
1323 | { |
1324 | if (!is<RenderBlockFlow>(*parent())) |
1325 | return; |
1326 | downcast<RenderBlockFlow>(*parent()).ensureLineBoxes(); |
1327 | } |
1328 | |
1329 | const SimpleLineLayout::Layout* RenderText::simpleLineLayout() const |
1330 | { |
1331 | if (!is<RenderBlockFlow>(*parent())) |
1332 | return nullptr; |
1333 | return downcast<RenderBlockFlow>(*parent()).simpleLineLayout(); |
1334 | } |
1335 | |
1336 | float 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 | |
1348 | float 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 | |
1382 | IntRect RenderText::linesBoundingBox() const |
1383 | { |
1384 | if (auto* layout = simpleLineLayout()) |
1385 | return SimpleLineLayout::computeBoundingBox(*this, *layout); |
1386 | |
1387 | return m_lineBoxes.boundingBox(*this); |
1388 | } |
1389 | |
1390 | LayoutRect RenderText::linesVisualOverflowBoundingBox() const |
1391 | { |
1392 | ASSERT(!simpleLineLayout()); |
1393 | return m_lineBoxes.visualOverflowBoundingBox(*this); |
1394 | } |
1395 | |
1396 | LayoutRect 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 | |
1412 | LayoutRect 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 | |
1458 | LayoutRect RenderText::collectSelectionRectsForLineBoxes(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent, Vector<LayoutRect>& rects) |
1459 | { |
1460 | return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, &rects); |
1461 | } |
1462 | |
1463 | LayoutRect RenderText::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent) |
1464 | { |
1465 | return collectSelectionRectsForLineBoxes(repaintContainer, clipToVisibleContent, nullptr); |
1466 | } |
1467 | |
1468 | int RenderText::caretMinOffset() const |
1469 | { |
1470 | if (auto* layout = simpleLineLayout()) |
1471 | return SimpleLineLayout::findCaretMinimumOffset(*this, *layout); |
1472 | return m_lineBoxes.caretMinOffset(); |
1473 | } |
1474 | |
1475 | int RenderText::caretMaxOffset() const |
1476 | { |
1477 | if (auto* layout = simpleLineLayout()) |
1478 | return SimpleLineLayout::findCaretMaximumOffset(*this, *layout); |
1479 | return m_lineBoxes.caretMaxOffset(*this); |
1480 | } |
1481 | |
1482 | unsigned RenderText::countRenderedCharacterOffsetsUntil(unsigned offset) const |
1483 | { |
1484 | ASSERT(!simpleLineLayout()); |
1485 | return m_lineBoxes.countCharacterOffsetsUntil(offset); |
1486 | } |
1487 | |
1488 | bool RenderText::containsRenderedCharacterOffset(unsigned offset) const |
1489 | { |
1490 | ASSERT(!simpleLineLayout()); |
1491 | return m_lineBoxes.containsOffset(*this, offset, RenderTextLineBoxes::CharacterOffset); |
1492 | } |
1493 | |
1494 | bool 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 | |
1501 | bool RenderText::hasRenderedText() const |
1502 | { |
1503 | if (auto* layout = simpleLineLayout()) |
1504 | return SimpleLineLayout::isTextRendered(*this, *layout); |
1505 | return m_lineBoxes.hasRenderedText(); |
1506 | } |
1507 | |
1508 | int 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 | |
1517 | int RenderText::previousOffsetForBackwardDeletion(int current) const |
1518 | { |
1519 | CachedTextBreakIterator iterator(text(), TextBreakIterator::Mode::Delete, nullAtom()); |
1520 | return iterator.preceding(current).valueOr(0); |
1521 | } |
1522 | |
1523 | int 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 | |
1532 | bool RenderText::computeCanUseSimpleFontCodePath() const |
1533 | { |
1534 | if (m_isAllASCII || text().is8Bit()) |
1535 | return true; |
1536 | return FontCascade::characterRangeCodePath(text().characters16(), length()) == FontCascade::Simple; |
1537 | } |
1538 | |
1539 | void 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 | |
1549 | StringView 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 | |
1560 | RenderInline* 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 | |
1569 | void 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 | |
1584 | RenderText* 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 | |