1/*
2 * Copyright (C) 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "RenderTextLineBoxes.h"
28
29#include "EllipsisBox.h"
30#include "InlineTextBox.h"
31#include "RenderBlock.h"
32#include "RenderStyle.h"
33#include "RenderView.h"
34#include "RootInlineBox.h"
35
36namespace WebCore {
37
38RenderTextLineBoxes::RenderTextLineBoxes()
39 : m_first(nullptr)
40 , m_last(nullptr)
41{
42}
43
44InlineTextBox* RenderTextLineBoxes::createAndAppendLineBox(RenderText& renderText)
45{
46 auto textBox = renderText.createTextBox();
47 if (!m_first) {
48 m_first = textBox.get();
49 m_last = textBox.get();
50 } else {
51 m_last->setNextTextBox(textBox.get());
52 textBox->setPreviousTextBox(m_last);
53 m_last = textBox.get();
54 }
55 return textBox.release();
56}
57
58void RenderTextLineBoxes::extract(InlineTextBox& box)
59{
60 checkConsistency();
61
62 m_last = box.prevTextBox();
63 if (&box == m_first)
64 m_first = nullptr;
65 if (box.prevTextBox())
66 box.prevTextBox()->setNextTextBox(nullptr);
67 box.setPreviousTextBox(nullptr);
68 for (auto* current = &box; current; current = current->nextTextBox())
69 current->setExtracted();
70
71 checkConsistency();
72}
73
74void RenderTextLineBoxes::attach(InlineTextBox& box)
75{
76 checkConsistency();
77
78 if (m_last) {
79 m_last->setNextTextBox(&box);
80 box.setPreviousTextBox(m_last);
81 } else
82 m_first = &box;
83 InlineTextBox* last = nullptr;
84 for (auto* current = &box; current; current = current->nextTextBox()) {
85 current->setExtracted(false);
86 last = current;
87 }
88 m_last = last;
89
90 checkConsistency();
91}
92
93void RenderTextLineBoxes::remove(InlineTextBox& box)
94{
95 checkConsistency();
96
97 if (&box == m_first)
98 m_first = box.nextTextBox();
99 if (&box == m_last)
100 m_last = box.prevTextBox();
101 if (box.nextTextBox())
102 box.nextTextBox()->setPreviousTextBox(box.prevTextBox());
103 if (box.prevTextBox())
104 box.prevTextBox()->setNextTextBox(box.nextTextBox());
105
106 checkConsistency();
107}
108
109void RenderTextLineBoxes::removeAllFromParent(RenderText& renderer)
110{
111 if (!m_first) {
112 if (renderer.parent())
113 renderer.parent()->dirtyLinesFromChangedChild(renderer);
114 return;
115 }
116 for (auto* box = m_first; box; box = box->nextTextBox())
117 box->removeFromParent();
118}
119
120void RenderTextLineBoxes::deleteAll()
121{
122 if (!m_first)
123 return;
124 InlineTextBox* next;
125 for (auto* current = m_first; current; current = next) {
126 next = current->nextTextBox();
127 delete current;
128 }
129 m_first = nullptr;
130 m_last = nullptr;
131}
132
133InlineTextBox* RenderTextLineBoxes::findNext(int offset, int& position) const
134{
135 if (!m_first)
136 return nullptr;
137 // FIXME: This looks buggy. The function is only used for debugging purposes.
138 auto current = m_first;
139 int currentOffset = current->len();
140 while (offset > currentOffset && current->nextTextBox()) {
141 current = current->nextTextBox();
142 currentOffset = current->start() + current->len();
143 }
144 // we are now in the correct text run
145 position = (offset > currentOffset ? current->len() : current->len() - (currentOffset - offset));
146 return current;
147}
148
149IntRect RenderTextLineBoxes::boundingBox(const RenderText& renderer) const
150{
151 if (!m_first)
152 return IntRect();
153
154 // Return the width of the minimal left side and the maximal right side.
155 float logicalLeftSide = 0;
156 float logicalRightSide = 0;
157 for (auto* current = m_first; current; current = current->nextTextBox()) {
158 if (current == m_first || current->logicalLeft() < logicalLeftSide)
159 logicalLeftSide = current->logicalLeft();
160 if (current == m_first || current->logicalRight() > logicalRightSide)
161 logicalRightSide = current->logicalRight();
162 }
163
164 bool isHorizontal = renderer.style().isHorizontalWritingMode();
165
166 float x = isHorizontal ? logicalLeftSide : m_first->x();
167 float y = isHorizontal ? m_first->y() : logicalLeftSide;
168 float width = isHorizontal ? logicalRightSide - logicalLeftSide : m_last->logicalBottom() - x;
169 float height = isHorizontal ? m_last->logicalBottom() - y : logicalRightSide - logicalLeftSide;
170 return enclosingIntRect(FloatRect(x, y, width, height));
171}
172
173IntPoint RenderTextLineBoxes::firstRunLocation() const
174{
175 if (!m_first)
176 return IntPoint();
177 return IntPoint(m_first->topLeft());
178}
179
180LayoutRect RenderTextLineBoxes::visualOverflowBoundingBox(const RenderText& renderer) const
181{
182 if (!m_first)
183 return LayoutRect();
184
185 // Return the width of the minimal left side and the maximal right side.
186 auto logicalLeftSide = LayoutUnit::max();
187 auto logicalRightSide = LayoutUnit::min();
188 for (auto* current = m_first; current; current = current->nextTextBox()) {
189 logicalLeftSide = std::min(logicalLeftSide, current->logicalLeftVisualOverflow());
190 logicalRightSide = std::max(logicalRightSide, current->logicalRightVisualOverflow());
191 }
192
193 auto logicalTop = m_first->logicalTopVisualOverflow();
194 auto logicalWidth = logicalRightSide - logicalLeftSide;
195 auto logicalHeight = m_last->logicalBottomVisualOverflow() - logicalTop;
196
197 LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight);
198 if (!renderer.style().isHorizontalWritingMode())
199 rect = rect.transposedRect();
200 return rect;
201}
202
203bool RenderTextLineBoxes::hasRenderedText() const
204{
205 for (auto* box = m_first; box; box = box->nextTextBox()) {
206 if (box->len())
207 return true;
208 }
209 return false;
210}
211
212int RenderTextLineBoxes::caretMinOffset() const
213{
214 auto box = m_first;
215 if (!box)
216 return 0;
217 int minOffset = box->start();
218 for (box = box->nextTextBox(); box; box = box->nextTextBox())
219 minOffset = std::min<int>(minOffset, box->start());
220 return minOffset;
221}
222
223int RenderTextLineBoxes::caretMaxOffset(const RenderText& renderer) const
224{
225 auto box = m_last;
226 if (!box)
227 return renderer.text().length();
228
229 int maxOffset = box->start() + box->len();
230 for (box = box->prevTextBox(); box; box = box->prevTextBox())
231 maxOffset = std::max<int>(maxOffset, box->start() + box->len());
232 return maxOffset;
233}
234
235bool RenderTextLineBoxes::containsOffset(const RenderText& renderer, unsigned offset, OffsetType type) const
236{
237 for (auto* box = m_first; box; box = box->nextTextBox()) {
238 if (offset < box->start() && !renderer.containsReversedText())
239 return false;
240 unsigned boxEnd = box->start() + box->len();
241 if (offset >= box->start() && offset <= boxEnd) {
242 if (offset == boxEnd && (type == CharacterOffset || box->isLineBreak()))
243 continue;
244 if (type == CharacterOffset)
245 return true;
246 // Return false for offsets inside composed characters.
247 return !offset || offset == static_cast<unsigned>(renderer.nextOffset(renderer.previousOffset(offset)));
248 }
249 }
250 return false;
251}
252
253unsigned RenderTextLineBoxes::countCharacterOffsetsUntil(unsigned offset) const
254{
255 unsigned result = 0;
256 for (auto* box = m_first; box; box = box->nextTextBox()) {
257 if (offset < box->start())
258 return result;
259 if (offset <= box->start() + box->len()) {
260 result += offset - box->start();
261 return result;
262 }
263 result += box->len();
264 }
265 return result;
266}
267
268enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart };
269
270static bool lineDirectionPointFitsInBox(int pointLineDirection, const InlineTextBox& box, ShouldAffinityBeDownstream& shouldAffinityBeDownstream)
271{
272 shouldAffinityBeDownstream = AlwaysDownstream;
273
274 // the x coordinate is equal to the left edge of this box
275 // the affinity must be downstream so the position doesn't jump back to the previous line
276 // except when box is the first box in the line
277 if (pointLineDirection <= box.logicalLeft()) {
278 shouldAffinityBeDownstream = !box.prevLeafChild() ? UpstreamIfPositionIsNotAtStart : AlwaysDownstream;
279 return true;
280 }
281
282#if !PLATFORM(IOS_FAMILY)
283 // and the x coordinate is to the left of the right edge of this box
284 // check to see if position goes in this box
285 if (pointLineDirection < box.logicalRight()) {
286 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
287 return true;
288 }
289#endif
290
291 // box is first on line
292 // and the x coordinate is to the left of the first text box left edge
293 if (!box.prevLeafChildIgnoringLineBreak() && pointLineDirection < box.logicalLeft())
294 return true;
295
296 if (!box.nextLeafChildIgnoringLineBreak()) {
297 // box is last on line
298 // and the x coordinate is to the right of the last text box right edge
299 // generate VisiblePosition, use UPSTREAM affinity if possible
300 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart;
301 return true;
302 }
303
304 return false;
305}
306
307static VisiblePosition createVisiblePositionForBox(const InlineBox& box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
308{
309 EAffinity affinity = VP_DEFAULT_AFFINITY;
310 switch (shouldAffinityBeDownstream) {
311 case AlwaysDownstream:
312 affinity = DOWNSTREAM;
313 break;
314 case AlwaysUpstream:
315 affinity = VP_UPSTREAM_IF_POSSIBLE;
316 break;
317 case UpstreamIfPositionIsNotAtStart:
318 affinity = offset > box.caretMinOffset() ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM;
319 break;
320 }
321 return box.renderer().createVisiblePosition(offset, affinity);
322}
323
324static VisiblePosition createVisiblePositionAfterAdjustingOffsetForBiDi(const InlineTextBox& box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream)
325{
326 ASSERT(offset >= 0);
327
328 if (offset && static_cast<unsigned>(offset) < box.len())
329 return createVisiblePositionForBox(box, box.start() + offset, shouldAffinityBeDownstream);
330
331 bool positionIsAtStartOfBox = !offset;
332 if (positionIsAtStartOfBox == box.isLeftToRightDirection()) {
333 // offset is on the left edge
334
335 const InlineBox* prevBox = box.prevLeafChildIgnoringLineBreak();
336 if ((prevBox && prevBox->bidiLevel() == box.bidiLevel())
337 || box.renderer().containingBlock()->style().direction() == box.direction()) // FIXME: left on 12CBA
338 return createVisiblePositionForBox(box, box.caretLeftmostOffset(), shouldAffinityBeDownstream);
339
340 if (prevBox && prevBox->bidiLevel() > box.bidiLevel()) {
341 // e.g. left of B in aDC12BAb
342 const InlineBox* leftmostBox;
343 do {
344 leftmostBox = prevBox;
345 prevBox = leftmostBox->prevLeafChildIgnoringLineBreak();
346 } while (prevBox && prevBox->bidiLevel() > box.bidiLevel());
347 return createVisiblePositionForBox(*leftmostBox, leftmostBox->caretRightmostOffset(), shouldAffinityBeDownstream);
348 }
349
350 if (!prevBox || prevBox->bidiLevel() < box.bidiLevel()) {
351 // e.g. left of D in aDC12BAb
352 const InlineBox* rightmostBox;
353 const InlineBox* nextBox = &box;
354 do {
355 rightmostBox = nextBox;
356 nextBox = rightmostBox->nextLeafChildIgnoringLineBreak();
357 } while (nextBox && nextBox->bidiLevel() >= box.bidiLevel());
358 return createVisiblePositionForBox(*rightmostBox,
359 box.isLeftToRightDirection() ? rightmostBox->caretMaxOffset() : rightmostBox->caretMinOffset(), shouldAffinityBeDownstream);
360 }
361
362 return createVisiblePositionForBox(box, box.caretRightmostOffset(), shouldAffinityBeDownstream);
363 }
364
365 const InlineBox* nextBox = box.nextLeafChildIgnoringLineBreak();
366 if ((nextBox && nextBox->bidiLevel() == box.bidiLevel())
367 || box.renderer().containingBlock()->style().direction() == box.direction())
368 return createVisiblePositionForBox(box, box.caretRightmostOffset(), shouldAffinityBeDownstream);
369
370 // offset is on the right edge
371 if (nextBox && nextBox->bidiLevel() > box.bidiLevel()) {
372 // e.g. right of C in aDC12BAb
373 const InlineBox* rightmostBox;
374 do {
375 rightmostBox = nextBox;
376 nextBox = rightmostBox->nextLeafChildIgnoringLineBreak();
377 } while (nextBox && nextBox->bidiLevel() > box.bidiLevel());
378 return createVisiblePositionForBox(*rightmostBox, rightmostBox->caretLeftmostOffset(), shouldAffinityBeDownstream);
379 }
380
381 if (!nextBox || nextBox->bidiLevel() < box.bidiLevel()) {
382 // e.g. right of A in aDC12BAb
383 const InlineBox* leftmostBox;
384 const InlineBox* prevBox = &box;
385 do {
386 leftmostBox = prevBox;
387 prevBox = leftmostBox->prevLeafChildIgnoringLineBreak();
388 } while (prevBox && prevBox->bidiLevel() >= box.bidiLevel());
389 return createVisiblePositionForBox(*leftmostBox,
390 box.isLeftToRightDirection() ? leftmostBox->caretMinOffset() : leftmostBox->caretMaxOffset(), shouldAffinityBeDownstream);
391 }
392
393 return createVisiblePositionForBox(box, box.caretLeftmostOffset(), shouldAffinityBeDownstream);
394}
395
396VisiblePosition RenderTextLineBoxes::positionForPoint(const RenderText& renderer, const LayoutPoint& point) const
397{
398 if (!m_first || !renderer.text().length())
399 return renderer.createVisiblePosition(0, DOWNSTREAM);
400
401 LayoutUnit pointLineDirection = m_first->isHorizontal() ? point.x() : point.y();
402 LayoutUnit pointBlockDirection = m_first->isHorizontal() ? point.y() : point.x();
403 bool blocksAreFlipped = renderer.style().isFlippedBlocksWritingMode();
404
405 InlineTextBox* lastBox = nullptr;
406 for (auto* box = m_first; box; box = box->nextTextBox()) {
407 if (box->isLineBreak() && !box->prevLeafChild() && box->nextLeafChild() && !box->nextLeafChild()->isLineBreak())
408 box = box->nextTextBox();
409
410 auto& rootBox = box->root();
411 LayoutUnit top = std::min(rootBox.selectionTop(), rootBox.lineTop());
412 if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirection == top)) {
413 LayoutUnit bottom = rootBox.selectionBottom();
414 if (rootBox.nextRootBox())
415 bottom = std::min(bottom, rootBox.nextRootBox()->lineTop());
416
417 if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockDirection == bottom)) {
418 ShouldAffinityBeDownstream shouldAffinityBeDownstream;
419#if PLATFORM(IOS_FAMILY)
420 if (pointLineDirection != box->logicalLeft() && point.x() < box->x() + box->logicalWidth()) {
421 int half = box->x() + box->logicalWidth() / 2;
422 EAffinity affinity = point.x() < half ? DOWNSTREAM : VP_UPSTREAM_IF_POSSIBLE;
423 return renderer.createVisiblePosition(box->offsetForPosition(pointLineDirection) + box->start(), affinity);
424 }
425#endif
426 if (lineDirectionPointFitsInBox(pointLineDirection, *box, shouldAffinityBeDownstream))
427 return createVisiblePositionAfterAdjustingOffsetForBiDi(*box, box->offsetForPosition(pointLineDirection), shouldAffinityBeDownstream);
428 }
429 }
430 lastBox = box;
431 }
432
433 if (lastBox) {
434 ShouldAffinityBeDownstream shouldAffinityBeDownstream;
435 lineDirectionPointFitsInBox(pointLineDirection, *lastBox, shouldAffinityBeDownstream);
436 return createVisiblePositionAfterAdjustingOffsetForBiDi(*lastBox, lastBox->offsetForPosition(pointLineDirection) + lastBox->start(), shouldAffinityBeDownstream);
437 }
438 return renderer.createVisiblePosition(0, DOWNSTREAM);
439}
440
441void RenderTextLineBoxes::setSelectionState(RenderText& renderer, RenderObject::SelectionState state)
442{
443 if (state == RenderObject::SelectionInside || state == RenderObject::SelectionNone) {
444 for (auto* box = m_first; box; box = box->nextTextBox())
445 box->root().setHasSelectedChildren(state == RenderObject::SelectionInside);
446 return;
447 }
448
449 auto start = renderer.view().selection().startPosition();
450 auto end = renderer.view().selection().endPosition();
451 if (state == RenderObject::SelectionStart) {
452 end = renderer.text().length();
453 // to handle selection from end of text to end of line
454 if (start && start == end)
455 start = end - 1;
456 } else if (state == RenderObject::SelectionEnd)
457 start = 0;
458
459 for (auto* box = m_first; box; box = box->nextTextBox()) {
460 if (box->isSelected(start, end))
461 box->root().setHasSelectedChildren(true);
462 }
463}
464
465static IntRect ellipsisRectForBox(const InlineTextBox& box, unsigned start, unsigned end)
466{
467 unsigned short truncation = box.truncation();
468 if (truncation == cNoTruncation)
469 return IntRect();
470
471 auto ellipsis = box.root().ellipsisBox();
472 if (!ellipsis)
473 return IntRect();
474
475 IntRect rect;
476 int ellipsisStartPosition = std::max<int>(start - box.start(), 0);
477 int ellipsisEndPosition = std::min<int>(end - box.start(), box.len());
478
479 // The ellipsis should be considered to be selected if the end of
480 // the selection is past the beginning of the truncation and the
481 // beginning of the selection is before or at the beginning of the truncation.
482 if (ellipsisEndPosition < truncation && ellipsisStartPosition > truncation)
483 return IntRect();
484 return ellipsis->selectionRect();
485}
486
487LayoutRect RenderTextLineBoxes::selectionRectForRange(unsigned start, unsigned end)
488{
489 LayoutRect rect;
490 for (auto* box = m_first; box; box = box->nextTextBox()) {
491 rect.unite(box->localSelectionRect(start, end));
492 rect.unite(ellipsisRectForBox(*box, start, end));
493 }
494 return rect;
495}
496
497void RenderTextLineBoxes::collectSelectionRectsForRange(unsigned start, unsigned end, Vector<LayoutRect>& rects)
498{
499 for (auto* box = m_first; box; box = box->nextTextBox()) {
500 LayoutRect rect;
501 rect.unite(box->localSelectionRect(start, end));
502 rect.unite(ellipsisRectForBox(*box, start, end));
503 if (!rect.size().isEmpty())
504 rects.append(rect);
505 }
506}
507
508Vector<IntRect> RenderTextLineBoxes::absoluteRects(const LayoutPoint& accumulatedOffset) const
509{
510 Vector<IntRect> rects;
511 for (auto* box = m_first; box; box = box->nextTextBox())
512 rects.append(enclosingIntRect(FloatRect(accumulatedOffset + box->topLeft(), box->size())));
513 return rects;
514}
515
516static FloatRect localQuadForTextBox(const InlineTextBox& box, unsigned start, unsigned end, bool useSelectionHeight)
517{
518 unsigned realEnd = std::min(box.end() + 1, end);
519 LayoutRect boxSelectionRect = box.localSelectionRect(start, realEnd);
520 if (!boxSelectionRect.height())
521 return FloatRect();
522 if (useSelectionHeight)
523 return boxSelectionRect;
524 // Change the height and y position (or width and x for vertical text)
525 // because selectionRect uses selection-specific values.
526 if (box.isHorizontal()) {
527 boxSelectionRect.setHeight(box.height());
528 boxSelectionRect.setY(box.y());
529 } else {
530 boxSelectionRect.setWidth(box.width());
531 boxSelectionRect.setX(box.x());
532 }
533 return boxSelectionRect;
534}
535
536Vector<IntRect> RenderTextLineBoxes::absoluteRectsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
537{
538 Vector<IntRect> rects;
539 for (auto* box = m_first; box; box = box->nextTextBox()) {
540 // Note: box->end() returns the index of the last character, not the index past it
541 if (start <= box->start() && box->end() < end) {
542 FloatRect boundaries = box->calculateBoundaries();
543 if (useSelectionHeight) {
544 LayoutRect selectionRect = box->localSelectionRect(start, end);
545 if (box->isHorizontal()) {
546 boundaries.setHeight(selectionRect.height());
547 boundaries.setY(selectionRect.y());
548 } else {
549 boundaries.setWidth(selectionRect.width());
550 boundaries.setX(selectionRect.x());
551 }
552 }
553 rects.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed).enclosingBoundingBox());
554 continue;
555 }
556 FloatRect rect = localQuadForTextBox(*box, start, end, useSelectionHeight);
557 if (!rect.isZero())
558 rects.append(renderer.localToAbsoluteQuad(rect, UseTransforms, wasFixed).enclosingBoundingBox());
559 }
560 return rects;
561}
562
563Vector<FloatQuad> RenderTextLineBoxes::absoluteQuads(const RenderText& renderer, bool* wasFixed, ClippingOption option) const
564{
565 Vector<FloatQuad> quads;
566 for (auto* box = m_first; box; box = box->nextTextBox()) {
567 FloatRect boundaries = box->calculateBoundaries();
568
569 // Shorten the width of this text box if it ends in an ellipsis.
570 // FIXME: ellipsisRectForBox should switch to return FloatRect soon with the subpixellayout branch.
571 IntRect ellipsisRect = (option == ClipToEllipsis) ? ellipsisRectForBox(*box, 0, renderer.text().length()) : IntRect();
572 if (!ellipsisRect.isEmpty()) {
573 if (renderer.style().isHorizontalWritingMode())
574 boundaries.setWidth(ellipsisRect.maxX() - boundaries.x());
575 else
576 boundaries.setHeight(ellipsisRect.maxY() - boundaries.y());
577 }
578 quads.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed));
579 }
580 return quads;
581}
582
583Vector<FloatQuad> RenderTextLineBoxes::absoluteQuadsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const
584{
585 Vector<FloatQuad> quads;
586 for (auto* box = m_first; box; box = box->nextTextBox()) {
587 // Note: box->end() returns the index of the last character, not the index past it
588 if (start <= box->start() && box->end() < end) {
589 FloatRect boundaries = box->calculateBoundaries();
590 if (useSelectionHeight) {
591 LayoutRect selectionRect = box->localSelectionRect(start, end);
592 if (box->isHorizontal()) {
593 boundaries.setHeight(selectionRect.height());
594 boundaries.setY(selectionRect.y());
595 } else {
596 boundaries.setWidth(selectionRect.width());
597 boundaries.setX(selectionRect.x());
598 }
599 }
600 quads.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed));
601 continue;
602 }
603 FloatRect rect = localQuadForTextBox(*box, start, end, useSelectionHeight);
604 if (!rect.isZero())
605 quads.append(renderer.localToAbsoluteQuad(rect, UseTransforms, wasFixed));
606 }
607 return quads;
608}
609
610void RenderTextLineBoxes::dirtyAll()
611{
612 for (auto* box = m_first; box; box = box->nextTextBox())
613 box->dirtyLineBoxes();
614}
615
616bool RenderTextLineBoxes::dirtyRange(RenderText& renderer, unsigned start, unsigned end, int lengthDelta)
617{
618 RootInlineBox* firstRootBox = nullptr;
619 RootInlineBox* lastRootBox = nullptr;
620
621 // Dirty all text boxes that include characters in between offset and offset+len.
622 bool dirtiedLines = false;
623 for (auto* current = m_first; current; current = current->nextTextBox()) {
624 // FIXME: This shouldn't rely on the end of a dirty line box. See https://bugs.webkit.org/show_bug.cgi?id=97264
625 // Text run is entirely before the affected range.
626 if (current->end() < start)
627 continue;
628 // Text run is entirely after the affected range.
629 if (current->start() > end) {
630 current->offsetRun(lengthDelta);
631 auto& rootBox = current->root();
632 if (!firstRootBox) {
633 firstRootBox = &rootBox;
634 if (!dirtiedLines) {
635 // The affected area was in between two runs. Mark the root box of the run after the affected area as dirty.
636 firstRootBox->markDirty();
637 dirtiedLines = true;
638 }
639 }
640 lastRootBox = &rootBox;
641 continue;
642 }
643 if (current->end() >= start && current->end() <= end) {
644 // Text run overlaps with the left end of the affected range.
645 current->dirtyLineBoxes();
646 dirtiedLines = true;
647 continue;
648 }
649 if (current->start() <= start && current->end() >= end) {
650 // Text run subsumes the affected range.
651 current->dirtyLineBoxes();
652 dirtiedLines = true;
653 continue;
654 }
655 if (current->start() <= end && current->end() >= end) {
656 // Text run overlaps with right end of the affected range.
657 current->dirtyLineBoxes();
658 dirtiedLines = true;
659 continue;
660 }
661 }
662
663 // Now we have to walk all of the clean lines and adjust their cached line break information
664 // to reflect our updated offsets.
665 if (lastRootBox)
666 lastRootBox = lastRootBox->nextRootBox();
667 if (firstRootBox) {
668 auto previousRootBox = firstRootBox->prevRootBox();
669 if (previousRootBox)
670 firstRootBox = previousRootBox;
671 } else if (m_last) {
672 ASSERT(!lastRootBox);
673 firstRootBox = &m_last->root();
674 firstRootBox->markDirty();
675 dirtiedLines = true;
676 }
677 for (auto* current = firstRootBox; current && current != lastRootBox; current = current->nextRootBox()) {
678 if (current->lineBreakObj() == &renderer && current->lineBreakPos() > end)
679 current->setLineBreakPos(current->lineBreakPos() + lengthDelta);
680 }
681
682 // If the text node is empty, dirty the line where new text will be inserted.
683 if (!m_first && renderer.parent()) {
684 renderer.parent()->dirtyLinesFromChangedChild(renderer);
685 dirtiedLines = true;
686 }
687 return dirtiedLines;
688}
689
690inline void RenderTextLineBoxes::checkConsistency() const
691{
692#if !ASSERT_DISABLED
693#ifdef CHECK_CONSISTENCY
694 const InlineTextBox* prev = nullptr;
695 for (auto* child = m_first; child; child = child->nextTextBox()) {
696 ASSERT(child->renderer() == this);
697 ASSERT(child->prevTextBox() == prev);
698 prev = child;
699 }
700 ASSERT(prev == m_last);
701#endif
702#endif
703}
704
705#if !ASSERT_DISABLED
706RenderTextLineBoxes::~RenderTextLineBoxes()
707{
708 ASSERT(!m_first);
709 ASSERT(!m_last);
710}
711#endif
712
713#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
714void RenderTextLineBoxes::invalidateParentChildLists()
715{
716 for (auto* box = m_first; box; box = box->nextTextBox())
717 box->invalidateParentChildList();
718}
719#endif
720
721}
722