1 /**
2 * This file is part of the html renderer for KDE.
3 *
4 * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org)
5 * (C) 2003-2007 Apple Computer, Inc.
6 * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
7 * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org)
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25 #include "rendering/bidi.h"
26 #include "rendering/break_lines.h"
27 #include "rendering/render_block.h"
28 #include "rendering/render_text.h"
29 #include "rendering/render_arena.h"
30 #include "rendering/render_layer.h"
31 #include "rendering/render_canvas.h"
32 #include "xml/dom_docimpl.h"
33 #include <QVector>
34
35 #include "QDebug"
36
37 #include <limits.h>
38
39 // SVG
40 #include "rendering/SVGRootInlineBox.h"
41 #include "rendering/SVGInlineTextBox.h"
42
43 #define BIDI_DEBUG 0
44 //#define DEBUG_LINEBREAKS
45 //#define PAGE_DEBUG
46
47 namespace khtml
48 {
49
50 // an iterator which goes through a BidiParagraph
51 struct BidiIterator {
BidiIteratorkhtml::BidiIterator52 BidiIterator() : par(nullptr), obj(nullptr), pos(0), endOfInline(false) {}
BidiIteratorkhtml::BidiIterator53 BidiIterator(RenderBlock *_par, RenderObject *_obj, unsigned int _pos, bool eoi = false) : par(_par), obj(_obj), pos(_pos), endOfInline(eoi) {}
54
55 void increment(BidiState *bidi = nullptr, bool skipInlines = true);
56
57 bool atEnd() const;
58
59 const QChar ¤t() const;
60 QChar::Direction direction() const;
61
62 RenderBlock *par;
63 RenderObject *obj;
64 unsigned int pos;
65 bool endOfInline;
66 };
67
68 struct BidiState {
BidiStatekhtml::BidiState69 BidiState() : context(nullptr) {}
70
71 BidiIterator sor;
72 BidiIterator eor;
73 BidiIterator last;
74 BidiIterator current;
75 BidiContext *context;
76 BidiStatus status;
77 };
78
79 // Used to track a list of chained bidi runs.
80 static BidiRun *sFirstBidiRun;
81 static BidiRun *sLastBidiRun;
82 static int sBidiRunCount;
83 static BidiRun *sCompactFirstBidiRun;
84 static BidiRun *sCompactLastBidiRun;
85 static int sCompactBidiRunCount;
86 static bool sBuildingCompactRuns;
87
88 // Midpoint globals. The goal is not to do any allocation when dealing with
89 // these midpoints, so we just keep an array around and never clear it. We track
90 // the number of items and position using the two other variables.
91 static QVector<BidiIterator> *smidpoints;
92 static uint sNumMidpoints;
93 static uint sCurrMidpoint;
94 static bool betweenMidpoints;
95
96 static bool isLineEmpty = true;
97 static bool previousLineBrokeAtBR = false;
98 static QChar::Direction dir = QChar::DirON;
99 static bool emptyRun = true;
100 static int numSpaces;
101
102 static void embed(QChar::Direction d, BidiState &bidi);
103 static void appendRun(BidiState &bidi);
104
getBPMWidth(int childValue,Length cssUnit)105 static int getBPMWidth(int childValue, Length cssUnit)
106 {
107 if (!cssUnit.isAuto()) {
108 return (cssUnit.isFixed() ? cssUnit.value() : childValue);
109 }
110 return 0;
111 }
112
getBorderPaddingMargin(RenderObject * child,bool endOfInline)113 static int getBorderPaddingMargin(RenderObject *child, bool endOfInline)
114 {
115 RenderStyle *cstyle = child->style();
116 int result = 0;
117 bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline;
118 result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()),
119 (leftSide ? cstyle->marginLeft() :
120 cstyle->marginRight()));
121 result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()),
122 (leftSide ? cstyle->paddingLeft() :
123 cstyle->paddingRight()));
124 result += leftSide ? child->borderLeft() : child->borderRight();
125 return result;
126 }
127
128 #ifndef NDEBUG
129 static bool inBidiRunDetach;
130 #endif
131
detach(RenderArena * renderArena)132 void BidiRun::detach(RenderArena *renderArena)
133 {
134 #ifndef NDEBUG
135 inBidiRunDetach = true;
136 #endif
137 delete this;
138 #ifndef NDEBUG
139 inBidiRunDetach = false;
140 #endif
141
142 // Recover the size left there for us by operator delete and free the memory.
143 renderArena->free(*(size_t *)this, this);
144 }
145
operator new(size_t sz,RenderArena * renderArena)146 void *BidiRun::operator new(size_t sz, RenderArena *renderArena) throw()
147 {
148 return renderArena->allocate(sz);
149 }
150
operator delete(void * ptr,size_t sz)151 void BidiRun::operator delete(void *ptr, size_t sz)
152 {
153 assert(inBidiRunDetach);
154
155 // Stash size where detach can find it.
156 *(size_t *)ptr = sz;
157 }
158
deleteBidiRuns(RenderArena * arena)159 static void deleteBidiRuns(RenderArena *arena)
160 {
161 if (!sFirstBidiRun) {
162 return;
163 }
164
165 BidiRun *curr = sFirstBidiRun;
166 while (curr) {
167 BidiRun *s = curr->nextRun;
168 curr->detach(arena);
169 curr = s;
170 }
171
172 sFirstBidiRun = nullptr;
173 sLastBidiRun = nullptr;
174 sBidiRunCount = 0;
175 }
176
177 // ---------------------------------------------------------------------
178
179 /* a small helper class used internally to resolve Bidi embedding levels.
180 Each line of text caches the embedding level at the start of the line for faster
181 relayouting
182 */
BidiContext(unsigned char l,QChar::Direction e,BidiContext * p,bool o)183 BidiContext::BidiContext(unsigned char l, QChar::Direction e, BidiContext *p, bool o)
184 : level(l), override(o), dir(e)
185 {
186 parent = p;
187 if (p) {
188 p->ref();
189 basicDir = p->basicDir;
190 } else {
191 basicDir = e;
192 }
193 count = 0;
194 }
195
~BidiContext()196 BidiContext::~BidiContext()
197 {
198 if (parent) {
199 parent->deref();
200 }
201 }
202
ref() const203 void BidiContext::ref() const
204 {
205 count++;
206 }
207
deref() const208 void BidiContext::deref() const
209 {
210 count--;
211 if (count <= 0) {
212 delete this;
213 }
214 }
215
216 // ---------------------------------------------------------------------
217
operator ==(const BidiContext & c1,const BidiContext & c2)218 inline bool operator==(const BidiContext &c1, const BidiContext &c2)
219 {
220 if (&c1 == &c2) {
221 return true;
222 }
223 if (c1.level != c2.level || c1.override != c2.override || c1.dir != c2.dir || c1.basicDir != c2.basicDir) {
224 return false;
225 }
226 if (!c1.parent) {
227 return !c2.parent;
228 }
229 return c2.parent && *c1.parent == *c2.parent;
230 }
231
operator ==(const BidiIterator & it1,const BidiIterator & it2)232 inline bool operator==(const BidiIterator &it1, const BidiIterator &it2)
233 {
234 if (it1.pos != it2.pos) {
235 return false;
236 }
237 if (it1.obj != it2.obj) {
238 return false;
239 }
240 return true;
241 }
242
operator !=(const BidiIterator & it1,const BidiIterator & it2)243 inline bool operator!=(const BidiIterator &it1, const BidiIterator &it2)
244 {
245 if (it1.pos != it2.pos) {
246 return true;
247 }
248 if (it1.obj != it2.obj) {
249 return true;
250 }
251 return false;
252 }
253
operator ==(const BidiStatus & status1,const BidiStatus & status2)254 inline bool operator==(const BidiStatus &status1, const BidiStatus &status2)
255 {
256 return status1.eor == status2.eor && status1.last == status2.last && status1.lastStrong == status2.lastStrong;
257 }
258
operator !=(const BidiStatus & status1,const BidiStatus & status2)259 inline bool operator!=(const BidiStatus &status1, const BidiStatus &status2)
260 {
261 return !(status1 == status2);
262 }
263
264 // when modifying this function, make sure you check InlineMinMaxIterator::next() as well.
Bidinext(RenderObject * par,RenderObject * current,BidiState * bidi=nullptr,bool skipInlines=true,bool * endOfInline=nullptr)265 static inline RenderObject *Bidinext(RenderObject *par, RenderObject *current, BidiState *bidi = nullptr,
266 bool skipInlines = true, bool *endOfInline = nullptr)
267 {
268 RenderObject *next = nullptr;
269 bool oldEndOfInline = endOfInline ? *endOfInline : false;
270 if (oldEndOfInline) {
271 *endOfInline = false;
272 }
273 while (current != nullptr) {
274 //qCDebug(KHTML_LOG) << "current = " << current;
275 if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned()) {
276 next = current->firstChild();
277 if (next && bidi) {
278 EUnicodeBidi ub = next->style()->unicodeBidi();
279 if (ub != UBNormal && !emptyRun) {
280 EDirection dir = next->style()->direction();
281 QChar::Direction d = (ub == Embed ? (dir == RTL ? QChar::DirRLE : QChar::DirLRE)
282 : (dir == RTL ? QChar::DirRLO : QChar::DirLRO));
283 embed(d, *bidi);
284 }
285 }
286 }
287 if (!next) {
288 if (!skipInlines && !oldEndOfInline && current->isInlineFlow() && endOfInline) {
289 next = current;
290 *endOfInline = true;
291 break;
292 }
293
294 while (current && current != par) {
295 next = current->nextSibling();
296 if (next) {
297 break;
298 }
299 if (bidi && current->style()->unicodeBidi() != UBNormal && !emptyRun) {
300 embed(QChar::DirPDF, *bidi);
301 }
302 current = current->parent();
303 if (!skipInlines && current && current != par && current->isInlineFlow() && endOfInline) {
304 next = current;
305 *endOfInline = true;
306 break;
307 }
308 }
309 }
310
311 if (!next) {
312 break;
313 }
314
315 if (next->isText() || next->isBR() || next->isFloating() || next->isReplaced() || next->isPositioned() || next->isGlyph()
316 || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines.
317 && next->isInlineFlow())) {
318 break;
319 }
320 current = next;
321 next = nullptr;
322 }
323 return next;
324 }
325
first(RenderObject * par,BidiState * bidi,bool skipInlines=true)326 static RenderObject *first(RenderObject *par, BidiState *bidi, bool skipInlines = true)
327 {
328 if (!par->firstChild()) {
329 return nullptr;
330 }
331 RenderObject *o = par->firstChild();
332
333 if (o->isInlineFlow()) {
334 if (skipInlines && o->firstChild()) {
335 o = Bidinext(par, o, bidi, skipInlines);
336 } else {
337 return o; // Never skip empty inlines.
338 }
339 }
340
341 if (o && !o->isText() && !o->isBR() && !o->isReplaced() && !o->isFloating() && !o->isPositioned() && !o->isGlyph()) {
342 o = Bidinext(par, o, bidi, skipInlines);
343 }
344 return o;
345 }
346
increment(BidiState * bidi,bool skipInlines)347 inline void BidiIterator::increment(BidiState *bidi, bool skipInlines)
348 {
349 if (!obj) {
350 return;
351 }
352 if (obj->isText()) {
353 pos++;
354 if (pos >= static_cast<RenderText *>(obj)->stringLength()) {
355 obj = Bidinext(par, obj, bidi, skipInlines);
356 pos = 0;
357 }
358 } else {
359 obj = Bidinext(par, obj, bidi, skipInlines, &endOfInline);
360 pos = 0;
361 }
362 }
363
atEnd() const364 inline bool BidiIterator::atEnd() const
365 {
366 if (!obj) {
367 return true;
368 }
369 return false;
370 }
371
current() const372 const QChar &BidiIterator::current() const
373 {
374 static QChar nonBreakingSpace(0xA0);
375
376 if (!obj || !obj->isText()) {
377 return nonBreakingSpace;
378 }
379
380 RenderText *text = static_cast<RenderText *>(obj);
381 if (!text->text()) {
382 return nonBreakingSpace;
383 }
384
385 return text->text()[pos];
386 }
387
direction() const388 inline QChar::Direction BidiIterator::direction() const
389 {
390 if (!obj || !obj->isText()) {
391 return QChar::DirON;
392 }
393
394 RenderText *renderTxt = static_cast<RenderText *>(obj);
395 if (pos >= renderTxt->stringLength()) {
396 return QChar::DirON;
397 }
398
399 return renderTxt->text()[pos].direction();
400 }
401
402 // -------------------------------------------------------------------------------------------------
403
addRun(BidiRun * bidiRun)404 static void addRun(BidiRun *bidiRun)
405 {
406 if (!sFirstBidiRun) {
407 sFirstBidiRun = sLastBidiRun = bidiRun;
408 } else {
409 sLastBidiRun->nextRun = bidiRun;
410 sLastBidiRun = bidiRun;
411 }
412 sBidiRunCount++;
413 bidiRun->compact = sBuildingCompactRuns;
414
415 // Compute the number of spaces in this run,
416 if (bidiRun->obj && bidiRun->obj->isText()) {
417 RenderText *text = static_cast<RenderText *>(bidiRun->obj);
418 if (text->text()) {
419 for (int i = bidiRun->start; i < bidiRun->stop; i++) {
420 const QChar c = text->text()[i];
421 if (c.unicode() == '\n' || c.category() == QChar::Separator_Space) {
422 numSpaces++;
423 }
424 }
425 }
426 }
427 }
428
reverseRuns(int start,int end)429 static void reverseRuns(int start, int end)
430 {
431 if (start >= end) {
432 return;
433 }
434
435 assert(start >= 0 && end < sBidiRunCount);
436
437 // Get the item before the start of the runs to reverse and put it in
438 // |beforeStart|. |curr| should point to the first run to reverse.
439 BidiRun *curr = sFirstBidiRun;
440 BidiRun *beforeStart = nullptr;
441 int i = 0;
442 while (i < start) {
443 i++;
444 beforeStart = curr;
445 curr = curr->nextRun;
446 }
447
448 BidiRun *startRun = curr;
449 while (i < end) {
450 i++;
451 curr = curr->nextRun;
452 }
453 BidiRun *endRun = curr;
454 BidiRun *afterEnd = curr->nextRun;
455
456 i = start;
457 curr = startRun;
458 BidiRun *newNext = afterEnd;
459 while (i <= end) {
460 // Do the reversal.
461 BidiRun *next = curr->nextRun;
462 curr->nextRun = newNext;
463 newNext = curr;
464 curr = next;
465 i++;
466 }
467
468 // Now hook up beforeStart and afterEnd to the newStart and newEnd.
469 if (beforeStart) {
470 beforeStart->nextRun = endRun;
471 } else {
472 sFirstBidiRun = endRun;
473 }
474
475 startRun->nextRun = afterEnd;
476 if (!afterEnd) {
477 sLastBidiRun = startRun;
478 }
479 }
480
chopMidpointsAt(RenderObject * obj,uint pos)481 static void chopMidpointsAt(RenderObject *obj, uint pos)
482 {
483 if (!sNumMidpoints) {
484 return;
485 }
486 BidiIterator *midpoints = smidpoints->data();
487 for (uint i = 0; i < sNumMidpoints; i++) {
488 const BidiIterator &point = midpoints[i];
489 if (point.obj == obj && point.pos == pos) {
490 sNumMidpoints = i;
491 break;
492 }
493 }
494 }
495
checkMidpoints(BidiIterator & lBreak)496 static void checkMidpoints(BidiIterator &lBreak)
497 {
498 // Check to see if our last midpoint is a start point beyond the line break. If so,
499 // shave it off the list, and shave off a trailing space if the previous end point isn't
500 // white-space: pre.
501 if (lBreak.obj && sNumMidpoints && sNumMidpoints % 2 == 0) {
502 BidiIterator *midpoints = smidpoints->data();
503 BidiIterator &endpoint = midpoints[sNumMidpoints - 2];
504 const BidiIterator &startpoint = midpoints[sNumMidpoints - 1];
505 BidiIterator currpoint = endpoint;
506 while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) {
507 currpoint.increment();
508 }
509 if (currpoint == lBreak) {
510 // We hit the line break before the start point. Shave off the start point.
511 sNumMidpoints--;
512 if (!endpoint.obj->style()->preserveWS()) {
513 if (endpoint.obj->isText()) {
514 // Don't shave a character off the endpoint if it was from a soft hyphen.
515 RenderText *textObj = static_cast<RenderText *>(endpoint.obj);
516 if (endpoint.pos + 1 < textObj->length() &&
517 textObj->text()[endpoint.pos + 1].unicode() == SOFT_HYPHEN) {
518 return;
519 }
520 }
521 endpoint.pos--;
522 }
523 }
524 }
525 }
526
addMidpoint(const BidiIterator & midpoint)527 static void addMidpoint(const BidiIterator &midpoint)
528 {
529 if (!smidpoints) {
530 return;
531 }
532
533 if (smidpoints->size() <= (int)sNumMidpoints) {
534 smidpoints->resize(sNumMidpoints + 10);
535 }
536
537 BidiIterator *midpoints = smidpoints->data();
538
539 // do not place midpoints in inline flows that are going to be skipped by the bidi iteration process.
540 // Place them at the next non-skippable object instead.
541 // #### eventually, we may want to have the same iteration in bidi and in findNextLineBreak,
542 // then this extra complexity can go away.
543 if (midpoint.obj && midpoint.obj->isInlineFlow() && (midpoint.obj->firstChild() || midpoint.endOfInline)) {
544 BidiIterator n = midpoint;
545 n.increment();
546 assert(!n.endOfInline);
547 // we'll recycle the endOfInline flag to mean : don't include this stop point, stop right before it.
548 // this is necessary because we just advanced our position to skip an inline, so we passed the real stop point
549 n.endOfInline = true;
550 if (!n.atEnd()) {
551 midpoints[sNumMidpoints++] = n;
552 }
553 } else {
554 assert(!midpoint.endOfInline);
555 midpoints[sNumMidpoints++] = midpoint;
556 }
557 }
558
appendRunsForObject(int start,int end,RenderObject * obj,BidiState & bidi)559 static void appendRunsForObject(int start, int end, RenderObject *obj, BidiState &bidi)
560 {
561 if (start > end || obj->isFloating() ||
562 (obj->isPositioned() && !obj->hasStaticX() && !obj->hasStaticY())) {
563 return;
564 }
565
566 bool haveNextMidpoint = (smidpoints && sCurrMidpoint < sNumMidpoints);
567 BidiIterator nextMidpoint;
568 if (haveNextMidpoint) {
569 nextMidpoint = smidpoints->at(sCurrMidpoint);
570 }
571 if (betweenMidpoints) {
572 if (!(haveNextMidpoint && nextMidpoint.obj == obj)) {
573 return;
574 }
575 // This is a new start point. Stop ignoring objects and
576 // adjust our start.
577 betweenMidpoints = false;
578 start = nextMidpoint.pos;
579 sCurrMidpoint++;
580 if (start < end) {
581 return appendRunsForObject(start, end, obj, bidi);
582 }
583 } else {
584 if (!smidpoints || !haveNextMidpoint || (obj != nextMidpoint.obj)) {
585 addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir));
586 return;
587 }
588
589 // An end midpoint has been encountered within our object. We
590 // need to go ahead and append a run with our endpoint.
591 if (int(nextMidpoint.pos + 1) <= end) {
592 betweenMidpoints = true;
593 sCurrMidpoint++;
594 if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it.
595 if (!nextMidpoint.endOfInline) // In this context, this flag means the stop point is exclusive, not inclusive (see addMidpoint).
596 addRun(new(obj->renderArena())
597 BidiRun(start, nextMidpoint.pos + 1, obj, bidi.context, dir));
598 return appendRunsForObject(nextMidpoint.pos + 1, end, obj, bidi);
599 }
600 } else {
601 addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir));
602 }
603 }
604 }
605
appendRun(BidiState & bidi)606 static void appendRun(BidiState &bidi)
607 {
608 if (emptyRun) {
609 return;
610 }
611 #if BIDI_DEBUG > 1
612 qCDebug(KHTML_LOG) << "appendRun: dir=" << (int)dir;
613 #endif
614
615 int start = bidi.sor.pos;
616 RenderObject *obj = bidi.sor.obj;
617 while (obj && obj != bidi.eor.obj) {
618 appendRunsForObject(start, obj->length(), obj, bidi);
619 start = 0;
620 obj = Bidinext(bidi.sor.par, obj);
621 }
622 if (obj) {
623 appendRunsForObject(start, bidi.eor.pos + 1, obj, bidi);
624 }
625
626 bidi.eor.increment();
627 bidi.sor = bidi.eor;
628 dir = QChar::DirON;
629 bidi.status.eor = QChar::DirON;
630 }
631
embed(QChar::Direction d,BidiState & bidi)632 static void embed(QChar::Direction d, BidiState &bidi)
633 {
634 #if BIDI_DEBUG > 1
635 qDebug("*** embed dir=%d emptyrun=%d", d, emptyRun);
636 #endif
637 if (d == QChar::DirPDF) {
638 BidiContext *c = bidi.context->parent;
639 if (c) {
640 if (bidi.eor != bidi.last) {
641 appendRun(bidi);
642 bidi.eor = bidi.last;
643 }
644 appendRun(bidi);
645 emptyRun = true;
646 bidi.status.last = bidi.context->dir;
647 bidi.context->deref();
648 bidi.context = c;
649 if (bidi.context->override) {
650 dir = bidi.context->dir;
651 } else {
652 dir = QChar::DirON;
653 }
654 bidi.status.lastStrong = bidi.context->dir;
655 }
656 } else {
657 QChar::Direction runDir;
658 if (d == QChar::DirRLE || d == QChar::DirRLO) {
659 runDir = QChar::DirR;
660 } else {
661 runDir = QChar::DirL;
662 }
663 bool override;
664 if (d == QChar::DirLRO || d == QChar::DirRLO) {
665 override = true;
666 } else {
667 override = false;
668 }
669
670 unsigned char level = bidi.context->level;
671 if (runDir == QChar::DirR) {
672 if (level % 2) { // we have an odd level
673 level += 2;
674 } else {
675 level++;
676 }
677 } else {
678 if (level % 2) { // we have an odd level
679 level++;
680 } else {
681 level += 2;
682 }
683 }
684
685 if (level < 61) {
686 if (bidi.eor != bidi.last) {
687 appendRun(bidi);
688 bidi.eor = bidi.last;
689 }
690 appendRun(bidi);
691 emptyRun = true;
692
693 bidi.context = new BidiContext(level, runDir, bidi.context, override);
694 bidi.context->ref();
695 dir = runDir;
696 bidi.status.last = runDir;
697 bidi.status.lastStrong = runDir;
698 bidi.status.eor = runDir;
699 }
700 }
701 }
702
createLineBoxes(RenderObject * obj)703 InlineFlowBox *RenderBlock::createLineBoxes(RenderObject *obj)
704 {
705 // See if we have an unconstructed line box for this object that is also
706 // the last item on the line.
707 KHTMLAssert(obj->isInlineFlow() || obj == this);
708 RenderFlow *flow = static_cast<RenderFlow *>(obj);
709
710 // Get the last box we made for this render object.
711 InlineFlowBox *box = flow->lastLineBox();
712
713 // If this box is constructed then it is from a previous line, and we need
714 // to make a new box for our line. If this box is unconstructed but it has
715 // something following it on the line, then we know we have to make a new box
716 // as well. In this situation our inline has actually been split in two on
717 // the same line (this can happen with very fancy language mixtures).
718 if (!box || box->isConstructed() || box->nextOnLine()) {
719 // We need to make a new box for this render object. Once
720 // made, we need to place it at the end of the current line.
721 InlineBox *newBox = obj->createInlineBox(false, obj == this);
722 KHTMLAssert(newBox->isInlineFlowBox());
723 box = static_cast<InlineFlowBox *>(newBox);
724 box->setFirstLineStyleBit(m_firstLine);
725
726 // We have a new box. Append it to the inline box we get by constructing our
727 // parent. If we have hit the block itself, then |box| represents the root
728 // inline box for the line, and it doesn't have to be appended to any parent
729 // inline.
730 if (obj != this) {
731 InlineFlowBox *parentBox = createLineBoxes(obj->parent());
732 parentBox->addToLine(box);
733 }
734 }
735
736 return box;
737 }
738
constructLine(const BidiIterator &,const BidiIterator & end)739 RootInlineBox *RenderBlock::constructLine(const BidiIterator &/*start*/, const BidiIterator &end)
740 {
741 if (!sFirstBidiRun) {
742 return nullptr; // We had no runs. Don't make a root inline box at all. The line is empty.
743 }
744
745 InlineFlowBox *parentBox = nullptr;
746 for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) {
747 // Create a box for our object.
748 r->box = r->obj->createInlineBox(r->obj->isPositioned(), false);
749
750 // If we have no parent box yet, or if the run is not simply a sibling,
751 // then we need to construct inline boxes as necessary to properly enclose the
752 // run's inline box.
753 if (!parentBox || (parentBox->object() != r->obj->parent()))
754 // Create new inline boxes all the way back to the appropriate insertion point.
755 {
756 parentBox = createLineBoxes(r->obj->parent());
757 }
758
759 // Append the inline box to this line.
760 parentBox->addToLine(r->box);
761 }
762
763 // We should have a root inline box. It should be unconstructed and
764 // be the last continuation of our line list.
765 KHTMLAssert(lastLineBox() && !lastLineBox()->isConstructed());
766
767 // Set bits on our inline flow boxes that indicate which sides should
768 // paint borders/margins/padding. This knowledge will ultimately be used when
769 // we determine the horizontal positions and widths of all the inline boxes on
770 // the line.
771 RenderObject *endObject = nullptr;
772 bool lastLine = !end.obj;
773 if (end.obj && end.pos == 0) {
774 endObject = end.obj;
775 }
776 lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject);
777
778 // Now mark the line boxes as being constructed.
779 lastLineBox()->setConstructed();
780
781 // Return the last line.
782 return lastRootBox();
783 }
784
computeHorizontalPositionsForLine(InlineFlowBox * lineBox,BidiState & bidi)785 void RenderBlock::computeHorizontalPositionsForLine(InlineFlowBox *lineBox, BidiState &bidi)
786 {
787 // First determine our total width.
788 int totWidth = lineBox->getFlowSpacingWidth();
789 BidiRun *r = nullptr;
790 for (r = sFirstBidiRun; r; r = r->nextRun) {
791 if (r->obj->isPositioned()) {
792 continue; // Positioned objects are only participating to figure out their
793 }
794 // correct static x position. They have no effect on the width.
795 if (r->obj->isText()) {
796 r->box->setWidth(static_cast<RenderText *>(r->obj)->width(r->start, r->stop - r->start, m_firstLine));
797 } else if (!r->obj->isInlineFlow()) {
798 r->obj->calcWidth(); // Is this really needed or the object width is already correct here ?
799 r->box->setWidth(r->obj->width());
800 totWidth += r->obj->marginLeft() + r->obj->marginRight();
801 }
802 totWidth += r->box->width();
803 }
804
805 // Armed with the total width of the line (without justification),
806 // we now examine our text-align property in order to determine where to position the
807 // objects horizontally. The total width of the line can be increased if we end up
808 // justifying text.
809 int x = leftOffset(m_height);
810 int availableWidth = lineWidth(m_height);
811 switch (style()->textAlign()) {
812 case LEFT:
813 case KHTML_LEFT:
814 if (style()->direction() == RTL && totWidth > availableWidth) {
815 x -= (totWidth - availableWidth);
816 }
817 numSpaces = 0;
818 break;
819 case JUSTIFY:
820 if (numSpaces != 0 && !bidi.current.atEnd() && !bidi.current.obj->isBR()) {
821 break;
822 }
823 // fall through
824 case TAAUTO:
825 numSpaces = 0;
826 // for right to left fall through to right aligned
827 if (bidi.context->basicDir == QChar::DirL) {
828 break;
829 }
830 case RIGHT:
831 case KHTML_RIGHT:
832 if (style()->direction() == RTL || totWidth < availableWidth) {
833 x += availableWidth - totWidth;
834 }
835 numSpaces = 0;
836 break;
837 case CENTER:
838 case KHTML_CENTER:
839 int xd = (availableWidth - totWidth) / 2;
840 x += xd > 0 ? xd : 0;
841 numSpaces = 0;
842 break;
843 }
844
845 if (numSpaces > 0) {
846 for (r = sFirstBidiRun; r; r = r->nextRun) {
847 int spaceAdd = 0;
848 if (numSpaces > 0 && r->obj->isText()) {
849 // get the number of spaces in the run
850 int spaces = 0;
851 for (int i = r->start; i < r->stop; i++) {
852 const QChar c = static_cast<RenderText *>(r->obj)->text()[i];
853 if (c.category() == QChar::Separator_Space || c == '\n') {
854 spaces++;
855 }
856 }
857
858 KHTMLAssert(spaces <= numSpaces);
859
860 // Only justify text with white-space: normal.
861 if (r->obj->style()->whiteSpace() == NORMAL) {
862 spaceAdd = (availableWidth - totWidth) * spaces / numSpaces;
863 spaceAdd = qMax(0, spaceAdd);
864 static_cast<InlineTextBox *>(r->box)->setSpaceAdd(spaceAdd);
865 totWidth += spaceAdd;
866 }
867 numSpaces -= spaces;
868 }
869 }
870 }
871
872 // The widths of all runs are now known. We can now place every inline box (and
873 // compute accurate widths for the inline flow boxes).
874 int rightPos = lineBox->placeBoxesHorizontally(x);
875 if (rightPos > m_overflowWidth) {
876 m_overflowWidth = rightPos; // FIXME: Work for rtl overflow also.
877 }
878 if (x < 0) {
879 m_overflowLeft = qMin(m_overflowLeft, x);
880 }
881 }
882
computeVerticalPositionsForLine(RootInlineBox * lineBox)883 void RenderBlock::computeVerticalPositionsForLine(RootInlineBox *lineBox)
884 {
885 lineBox->verticallyAlignBoxes(m_height);
886 lineBox->setBlockHeight(m_height);
887
888 // Check for page-breaks
889 if (canvas()->pagedMode() && !lineBox->afterPageBreak())
890 // If we get a page-break we might need to redo the line-break
891 if (clearLineOfPageBreaks(lineBox) && hasFloats()) {
892 return;
893 }
894
895 // See if the line spilled out. If so set overflow height accordingly.
896 int bottomOfLine = lineBox->bottomOverflow();
897 if (bottomOfLine > m_height && bottomOfLine > m_overflowHeight) {
898 m_overflowHeight = bottomOfLine;
899 }
900
901 bool beforeContent = true;
902
903 // Now make sure we place replaced render objects correctly.
904 for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) {
905
906 // For positioned placeholders, cache the static Y position an object with non-inline display would have.
907 // Either it is unchanged if it comes before any real linebox, or it must clear the current line (already accounted in m_height).
908 // This value will be picked up by position() if relevant.
909 if (r->obj->isPositioned()) {
910 r->box->setYPos(beforeContent && r->obj->isBox() ? static_cast<RenderBox *>(r->obj)->staticY() : m_height);
911 } else if (beforeContent) {
912 beforeContent = false;
913 }
914
915 // Position is used to properly position both replaced elements and
916 // to update the static normal flow x/y of positioned elements.
917 r->obj->position(r->box, r->start, r->stop - r->start, r->level % 2);
918 }
919 }
920
clearLineOfPageBreaks(InlineFlowBox * lineBox)921 bool RenderBlock::clearLineOfPageBreaks(InlineFlowBox *lineBox)
922 {
923 bool doPageBreak = false;
924 // Check for physical page-breaks
925 int xpage = crossesPageBreak(lineBox->topOverflow(), lineBox->bottomOverflow());
926 if (xpage) {
927 #ifdef PAGE_DEBUG
928 qCDebug(KHTML_LOG) << renderName() << " Line crosses to page " << xpage;
929 qCDebug(KHTML_LOG) << renderName() << " at pos " << lineBox->yPos() << " height " << lineBox->height();
930 #endif
931
932 doPageBreak = true;
933 // check page-break-inside
934 if (!style()->pageBreakInside()) {
935 if (parent()->canClear(this, PageBreakNormal)) {
936 setNeedsPageClear(true);
937 doPageBreak = false;
938 }
939 #ifdef PAGE_DEBUG
940 else {
941 qCDebug(KHTML_LOG) << "Ignoring page-break-inside: avoid";
942 }
943 #endif
944 }
945 // check orphans
946 int orphans = 0;
947 InlineRunBox *box = lineBox->prevLineBox();
948 while (box && orphans < style()->orphans()) {
949 orphans++;
950 box = box->prevLineBox();
951 }
952
953 if (orphans == 0) {
954 setNeedsPageClear(true);
955 doPageBreak = false;
956 } else if (orphans < style()->orphans()) {
957 #ifdef PAGE_DEBUG
958 qCDebug(KHTML_LOG) << "Orphans: " << orphans;
959 #endif
960 // Orphans is a level 2 page-break rule and can be broken only
961 // if the break is physically required.
962 if (parent()->canClear(this, PageBreakHarder)) {
963 // move block instead
964 setNeedsPageClear(true);
965 doPageBreak = false;
966 }
967 #ifdef PAGE_DEBUG
968 else {
969 qCDebug(KHTML_LOG) << "Ignoring violated orphans";
970 }
971 #endif
972 }
973 if (doPageBreak) {
974 #ifdef PAGE_DEBUG
975 int oldYPos = lineBox->yPos();
976 #endif
977 int pTop = pageTopAfter(lineBox->yPos());
978 m_height = pTop;
979 lineBox->setAfterPageBreak(true);
980 lineBox->verticallyAlignBoxes(m_height);
981 if (lineBox->yPos() < pTop) {
982 // ### serious crap. render_line is sometimes placing lines too high
983 // qCDebug(KHTML_LOG) << "page top overflow by repositioned line";
984 int heightIncrease = pTop - lineBox->yPos();
985 m_height = pTop + heightIncrease;
986 lineBox->verticallyAlignBoxes(m_height);
987 }
988 #ifdef PAGE_DEBUG
989 qCDebug(KHTML_LOG) << "Cleared line " << lineBox->yPos() - oldYPos << "px";
990 #endif
991 setContainsPageBreak(true);
992 }
993 }
994 return doPageBreak;
995 }
996
997 // collects one line of the paragraph and transforms it to visual order
bidiReorderLine(const BidiIterator & start,const BidiIterator & end,BidiState & bidi)998 void RenderBlock::bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi)
999 {
1000 if (start == end) {
1001 if (start.current() == '\n') {
1002 m_height += lineHeight(m_firstLine);
1003 }
1004 return;
1005 }
1006
1007 #if BIDI_DEBUG > 1
1008 qCDebug(KHTML_LOG) << "reordering Line from " << start.obj << "/" << start.pos << " to " << end.obj << "/" << end.pos;
1009 #endif
1010
1011 sFirstBidiRun = nullptr;
1012 sLastBidiRun = nullptr;
1013 sBidiRunCount = 0;
1014
1015 // context->ref();
1016
1017 dir = QChar::DirON;
1018 emptyRun = true;
1019
1020 numSpaces = 0;
1021
1022 bidi.current = start;
1023 bidi.last = bidi.current;
1024 bool atEnd = false;
1025 while (1) {
1026 QChar::Direction dirCurrent;
1027 if (atEnd) {
1028 //qCDebug(KHTML_LOG) << "atEnd";
1029 BidiContext *c = bidi.context;
1030 if (bidi.current.atEnd())
1031 while (c->parent) {
1032 c = c->parent;
1033 }
1034 dirCurrent = c->dir;
1035 } else if (bidi.context->override) {
1036 dirCurrent = bidi.context->dir;
1037 } else {
1038 dirCurrent = bidi.current.direction();
1039 }
1040
1041 #ifndef QT_NO_UNICODETABLES
1042
1043 #if BIDI_DEBUG > 1
1044 qCDebug(KHTML_LOG) << "directions: dir=" << (int)dir << " current=" << (int)dirCurrent << " last=" << bidi.status.last << " eor=" << bidi.status.eor << " lastStrong=" << bidi.status.lastStrong << " embedding=" << (int)bidi.context->dir << " level =" << (int)bidi.context->level;
1045 #endif
1046
1047 switch (dirCurrent) {
1048
1049 // embedding and overrides (X1-X9 in the Bidi specs)
1050 case QChar::DirRLE:
1051 case QChar::DirLRE:
1052 case QChar::DirRLO:
1053 case QChar::DirLRO:
1054 case QChar::DirPDF:
1055 embed(dirCurrent, bidi);
1056 break;
1057
1058 // strong types
1059 case QChar::DirL:
1060 if (dir == QChar::DirON) {
1061 dir = QChar::DirL;
1062 }
1063 switch (bidi.status.last) {
1064 case QChar::DirL:
1065 bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; break;
1066 case QChar::DirR:
1067 case QChar::DirAL:
1068 case QChar::DirEN:
1069 case QChar::DirAN:
1070 appendRun(bidi);
1071 break;
1072 case QChar::DirES:
1073 case QChar::DirET:
1074 case QChar::DirCS:
1075 case QChar::DirBN:
1076 case QChar::DirB:
1077 case QChar::DirS:
1078 case QChar::DirWS:
1079 case QChar::DirON:
1080 if (bidi.status.eor != QChar::DirL) {
1081 //last stuff takes embedding dir
1082 if (bidi.context->dir == QChar::DirL || bidi.status.lastStrong == QChar::DirL) {
1083 if (bidi.status.eor != QChar::DirEN && bidi.status.eor != QChar::DirAN && bidi.status.eor != QChar::DirON) {
1084 appendRun(bidi);
1085 }
1086 dir = QChar::DirL;
1087 bidi.eor = bidi.current;
1088 bidi.status.eor = QChar::DirL;
1089 } else {
1090 if (bidi.status.eor == QChar::DirEN || bidi.status.eor == QChar::DirAN) {
1091 dir = bidi.status.eor;
1092 appendRun(bidi);
1093 }
1094 dir = QChar::DirR;
1095 bidi.eor = bidi.last;
1096 appendRun(bidi);
1097 dir = QChar::DirL;
1098 bidi.status.eor = QChar::DirL;
1099 }
1100 } else {
1101 bidi.eor = bidi.current; bidi.status.eor = QChar::DirL;
1102 }
1103 default:
1104 break;
1105 }
1106 bidi.status.lastStrong = QChar::DirL;
1107 break;
1108 case QChar::DirAL:
1109 case QChar::DirR:
1110 if (dir == QChar::DirON) {
1111 dir = QChar::DirR;
1112 }
1113 switch (bidi.status.last) {
1114 case QChar::DirR:
1115 case QChar::DirAL:
1116 bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; break;
1117 case QChar::DirL:
1118 case QChar::DirEN:
1119 case QChar::DirAN:
1120 appendRun(bidi);
1121 dir = QChar::DirR;
1122 bidi.eor = bidi.current;
1123 bidi.status.eor = QChar::DirR;
1124 break;
1125 case QChar::DirES:
1126 case QChar::DirET:
1127 case QChar::DirCS:
1128 case QChar::DirBN:
1129 case QChar::DirB:
1130 case QChar::DirS:
1131 case QChar::DirWS:
1132 case QChar::DirON:
1133 if (!(bidi.status.eor == QChar::DirR) && !(bidi.status.eor == QChar::DirAL)) {
1134 //last stuff takes embedding dir
1135 if (bidi.context->dir == QChar::DirR || bidi.status.lastStrong == QChar::DirR
1136 || bidi.status.lastStrong == QChar::DirAL) {
1137 appendRun(bidi);
1138 dir = QChar::DirR;
1139 bidi.eor = bidi.current;
1140 bidi.status.eor = QChar::DirR;
1141 } else {
1142 dir = QChar::DirL;
1143 bidi.eor = bidi.last;
1144 appendRun(bidi);
1145 dir = QChar::DirR;
1146 bidi.status.eor = QChar::DirR;
1147 }
1148 } else {
1149 bidi.eor = bidi.current; bidi.status.eor = QChar::DirR;
1150 }
1151 default:
1152 break;
1153 }
1154 bidi.status.lastStrong = dirCurrent;
1155 break;
1156
1157 // weak types:
1158
1159 case QChar::DirNSM:
1160 // ### if @sor, set dir to dirSor
1161 break;
1162 case QChar::DirEN:
1163 if (!(bidi.status.lastStrong == QChar::DirAL)) {
1164 // if last strong was AL change EN to AN
1165 if (dir == QChar::DirON) {
1166 dir = QChar::DirL;
1167 }
1168 switch (bidi.status.last) {
1169 case QChar::DirET:
1170 if (bidi.status.lastStrong == QChar::DirR || bidi.status.lastStrong == QChar::DirAL) {
1171 appendRun(bidi);
1172 dir = QChar::DirEN;
1173 bidi.status.eor = QChar::DirEN;
1174 }
1175 // fall through
1176 case QChar::DirEN:
1177 case QChar::DirL:
1178 bidi.eor = bidi.current;
1179 bidi.status.eor = dirCurrent;
1180 break;
1181 case QChar::DirR:
1182 case QChar::DirAL:
1183 case QChar::DirAN:
1184 appendRun(bidi);
1185 bidi.status.eor = QChar::DirEN;
1186 dir = QChar::DirEN; break;
1187 case QChar::DirES:
1188 case QChar::DirCS:
1189 if (bidi.status.eor == QChar::DirEN) {
1190 bidi.eor = bidi.current; break;
1191 }
1192 case QChar::DirBN:
1193 case QChar::DirB:
1194 case QChar::DirS:
1195 case QChar::DirWS:
1196 case QChar::DirON:
1197 if (bidi.status.eor == QChar::DirR) {
1198 // neutrals go to R
1199 bidi.eor = bidi.last;
1200 appendRun(bidi);
1201 dir = QChar::DirEN;
1202 bidi.status.eor = QChar::DirEN;
1203 } else if (bidi.status.eor == QChar::DirL ||
1204 (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) {
1205 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1206 } else {
1207 // numbers on both sides, neutrals get right to left direction
1208 if (dir != QChar::DirL) {
1209 appendRun(bidi);
1210 bidi.eor = bidi.last;
1211 dir = QChar::DirR;
1212 appendRun(bidi);
1213 dir = QChar::DirEN;
1214 bidi.status.eor = QChar::DirEN;
1215 } else {
1216 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1217 }
1218 }
1219 default:
1220 break;
1221 }
1222 break;
1223 }
1224 case QChar::DirAN:
1225 dirCurrent = QChar::DirAN;
1226 if (dir == QChar::DirON) {
1227 dir = QChar::DirAN;
1228 }
1229 switch (bidi.status.last) {
1230 case QChar::DirL:
1231 case QChar::DirAN:
1232 bidi.eor = bidi.current; bidi.status.eor = QChar::DirAN; break;
1233 case QChar::DirR:
1234 case QChar::DirAL:
1235 case QChar::DirEN:
1236 appendRun(bidi);
1237 dir = QChar::DirAN; bidi.status.eor = QChar::DirAN;
1238 break;
1239 case QChar::DirCS:
1240 if (bidi.status.eor == QChar::DirAN) {
1241 bidi.eor = bidi.current; break;
1242 }
1243 case QChar::DirES:
1244 case QChar::DirET:
1245 case QChar::DirBN:
1246 case QChar::DirB:
1247 case QChar::DirS:
1248 case QChar::DirWS:
1249 case QChar::DirON:
1250 if (bidi.status.eor == QChar::DirR) {
1251 // neutrals go to R
1252 bidi.eor = bidi.last;
1253 appendRun(bidi);
1254 dir = QChar::DirAN;
1255 bidi.status.eor = QChar::DirAN;
1256 } else if (bidi.status.eor == QChar::DirL ||
1257 (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) {
1258 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1259 } else {
1260 // numbers on both sides, neutrals get right to left direction
1261 if (dir != QChar::DirL) {
1262 appendRun(bidi);
1263 bidi.eor = bidi.last;
1264 dir = QChar::DirR;
1265 appendRun(bidi);
1266 dir = QChar::DirAN;
1267 bidi.status.eor = QChar::DirAN;
1268 } else {
1269 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1270 }
1271 }
1272 default:
1273 break;
1274 }
1275 break;
1276 case QChar::DirES:
1277 case QChar::DirCS:
1278 break;
1279 case QChar::DirET:
1280 if (bidi.status.last == QChar::DirEN) {
1281 dirCurrent = QChar::DirEN;
1282 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1283 break;
1284 }
1285 break;
1286
1287 // boundary neutrals should be ignored
1288 case QChar::DirBN:
1289 break;
1290 // neutrals
1291 case QChar::DirB:
1292 // ### what do we do with newline and paragraph separators that come to here?
1293 break;
1294 case QChar::DirS:
1295 // ### implement rule L1
1296 break;
1297 case QChar::DirWS:
1298 break;
1299 case QChar::DirON:
1300 break;
1301 default:
1302 break;
1303 }
1304
1305 //cout << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << endl;
1306
1307 if (bidi.current.atEnd()) {
1308 break;
1309 }
1310
1311 // set status.last as needed.
1312 switch (dirCurrent) {
1313 case QChar::DirET:
1314 case QChar::DirES:
1315 case QChar::DirCS:
1316 case QChar::DirS:
1317 case QChar::DirWS:
1318 case QChar::DirON:
1319 switch (bidi.status.last) {
1320 case QChar::DirL:
1321 case QChar::DirR:
1322 case QChar::DirAL:
1323 case QChar::DirEN:
1324 case QChar::DirAN:
1325 bidi.status.last = dirCurrent;
1326 break;
1327 default:
1328 bidi.status.last = QChar::DirON;
1329 }
1330 break;
1331 case QChar::DirNSM:
1332 case QChar::DirBN:
1333 // ignore these
1334 break;
1335 case QChar::DirEN:
1336 if (bidi.status.last == QChar::DirL) {
1337 break;
1338 }
1339 // fall through
1340 default:
1341 bidi.status.last = dirCurrent;
1342 }
1343 #endif
1344
1345 if (atEnd) {
1346 break;
1347 }
1348 bidi.last = bidi.current;
1349
1350 if (emptyRun) {
1351 bidi.sor = bidi.current;
1352 bidi.eor = bidi.current;
1353 emptyRun = false;
1354 }
1355
1356 // this causes the operator ++ to open and close embedding levels as needed
1357 // for the CSS unicode-bidi property
1358 bidi.current.increment(&bidi);
1359
1360 if (bidi.current == end) {
1361 if (emptyRun) {
1362 break;
1363 }
1364 atEnd = true;
1365 }
1366 }
1367
1368 #if BIDI_DEBUG > 0
1369 qCDebug(KHTML_LOG) << "reached end of line current=" << bidi.current.obj << "/" << bidi.current.pos
1370 << ", eor=" << bidi.eor.obj << "/" << bidi.eor.pos;
1371 #endif
1372 if (!emptyRun && bidi.sor != bidi.current) {
1373 bidi.eor = bidi.last;
1374 appendRun(bidi);
1375 }
1376
1377 // reorder line according to run structure...
1378
1379 // first find highest and lowest levels
1380 uchar levelLow = 128;
1381 uchar levelHigh = 0;
1382 BidiRun *r = sFirstBidiRun;
1383 while (r) {
1384 if (r->level > levelHigh) {
1385 levelHigh = r->level;
1386 }
1387 if (r->level < levelLow) {
1388 levelLow = r->level;
1389 }
1390 r = r->nextRun;
1391 }
1392
1393 // implements reordering of the line (L2 according to Bidi spec):
1394 // L2. From the highest level found in the text to the lowest odd level on each line,
1395 // reverse any contiguous sequence of characters that are at that level or higher.
1396
1397 // reversing is only done up to the lowest odd level
1398 if (!(levelLow % 2)) {
1399 levelLow++;
1400 }
1401
1402 int count = sBidiRunCount - 1;
1403
1404 // do not reverse for visually ordered web sites
1405 if (!style()->visuallyOrdered()) {
1406 while (levelHigh >= levelLow) {
1407 int i = 0;
1408 BidiRun *currRun = sFirstBidiRun;
1409 while (i < count) {
1410 while (i < count && currRun && currRun->level < levelHigh) {
1411 i++;
1412 currRun = currRun->nextRun;
1413 }
1414 int start = i;
1415 while (i <= count && currRun && currRun->level >= levelHigh) {
1416 i++;
1417 currRun = currRun->nextRun;
1418 }
1419 int end = i - 1;
1420 reverseRuns(start, end);
1421 }
1422 levelHigh--;
1423 }
1424 }
1425
1426 #if BIDI_DEBUG > 0
1427 qCDebug(KHTML_LOG) << "visual order is:";
1428 for (BidiRun *curr = sFirstBidiRun; curr; curr = curr->nextRun) {
1429 qCDebug(KHTML_LOG) << " " << curr;
1430 }
1431 #endif
1432 }
1433
layoutInlineChildren(bool relayoutChildren,int breakBeforeLine)1434 void RenderBlock::layoutInlineChildren(bool relayoutChildren, int breakBeforeLine)
1435 {
1436 BidiState bidi;
1437
1438 m_overflowHeight = 0;
1439
1440 invalidateVerticalPosition();
1441 #ifdef DEBUG_LAYOUT
1442 QTime qt;
1443 qt.start();
1444 qCDebug(KHTML_LOG) << renderName() << " layoutInlineChildren( " << this << " )";
1445 #endif
1446 #if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS )
1447 qCDebug(KHTML_LOG) << " ------- bidi start " << this << " -------";
1448 #endif
1449
1450 m_height = borderTop() + paddingTop();
1451 int toAdd = borderBottom() + paddingBottom();
1452 if (m_layer && scrollsOverflowX() && style()->height().isAuto()) {
1453 toAdd += m_layer->horizontalScrollbarHeight();
1454 }
1455
1456 // Figure out if we should clear our line boxes.
1457 bool fullLayout = !firstLineBox() || !firstChild() || selfNeedsLayout() || relayoutChildren || hasFloats();
1458
1459 if (fullLayout) {
1460 deleteInlineBoxes();
1461 }
1462
1463 // Text truncation only kicks in if your overflow isn't visible and your
1464 // text-overflow-mode isn't clip.
1465 bool hasTextOverflow = style()->textOverflow() && hasOverflowClip();
1466
1467 // Walk all the lines and delete our ellipsis line boxes if they exist.
1468 if (hasTextOverflow) {
1469 deleteEllipsisLineBoxes();
1470 }
1471
1472 if (firstChild()) {
1473 // layout replaced elements
1474 RenderObject *o = first(this, nullptr, false);
1475 while (o) {
1476 invalidateVerticalPosition();
1477 if (!fullLayout && o->markedForRepaint()) {
1478 o->repaintDuringLayout();
1479 o->setMarkedForRepaint(false);
1480 }
1481 if (o->isReplaced() || o->isFloating() || o->isPositioned()) {
1482
1483 if ((!o->isPositioned() || o->isPosWithStaticDim()) &&
1484 (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent())) {
1485 o->setChildNeedsLayout(true, false);
1486 }
1487
1488 if (o->isPositioned()) {
1489 if (!o->inPosObjectList()) {
1490 o->containingBlock()->insertPositionedObject(o);
1491 }
1492 if (fullLayout) {
1493 static_cast<RenderBox *>(o)->RenderBox::deleteInlineBoxes();
1494 }
1495 } else {
1496 if (fullLayout || o->needsLayout()) {
1497 static_cast<RenderBox *>(o)->RenderBox::dirtyInlineBoxes(fullLayout);
1498 }
1499 o->layoutIfNeeded();
1500 }
1501 } else {
1502 if (fullLayout || o->selfNeedsLayout()) {
1503 o->dirtyInlineBoxes(fullLayout);
1504 o->setMarkedForRepaint(false);
1505 }
1506 o->setNeedsLayout(false);
1507 }
1508 o = Bidinext(this, o, nullptr, false);
1509 }
1510
1511 BidiContext *startEmbed;
1512 if (style()->direction() == LTR) {
1513 startEmbed = new BidiContext(0, QChar::DirL);
1514 bidi.status.eor = QChar::DirL;
1515 } else {
1516 startEmbed = new BidiContext(1, QChar::DirR);
1517 bidi.status.eor = QChar::DirR;
1518 }
1519 startEmbed->ref();
1520
1521 bidi.status.lastStrong = QChar::DirON;
1522 bidi.status.last = QChar::DirON;
1523
1524 bidi.context = startEmbed;
1525
1526 // We want to skip ahead to the first dirty line
1527 BidiIterator start;
1528 RootInlineBox *startLine = determineStartPosition(fullLayout, start, bidi);
1529
1530 // Then look forward to see if we can find a clean area that is clean up to the end.
1531 BidiIterator cleanLineStart;
1532 BidiStatus cleanLineBidiStatus;
1533 BidiContext *cleanLineBidiContext = nullptr;
1534 int endLineYPos = 0;
1535 RootInlineBox *endLine = (fullLayout || !startLine) ?
1536 nullptr : determineEndPosition(startLine, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLineYPos);
1537
1538 // Extract the clean area. We will add it back if we determine that we're able to
1539 // synchronize after relayouting the dirty area.
1540 if (endLine)
1541 for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) {
1542 line->extractLine();
1543 }
1544
1545 // Delete the dirty area.
1546 if (startLine) {
1547 RenderArena *arena = renderArena();
1548 RootInlineBox *box = startLine;
1549 while (box) {
1550 RootInlineBox *next = box->nextRootBox();
1551 box->deleteLine(arena);
1552 box = next;
1553 }
1554 startLine = nullptr;
1555 }
1556 BidiIterator end = start;
1557 bool endLineMatched = false;
1558 m_firstLine = true;
1559
1560 if (!smidpoints) {
1561 smidpoints = new QVector<BidiIterator>;
1562 }
1563
1564 sNumMidpoints = 0;
1565 sCurrMidpoint = 0;
1566 sCompactFirstBidiRun = sCompactLastBidiRun = nullptr;
1567 sCompactBidiRunCount = 0;
1568
1569 previousLineBrokeAtBR = true;
1570
1571 int lineCount = 0;
1572 bool pagebreakHint = false;
1573 int oldPos = 0;
1574 BidiIterator oldStart;
1575 BidiState oldBidi;
1576 const bool pagedMode = canvas()->pagedMode();
1577
1578 while (!end.atEnd()) {
1579 start = end;
1580 if (endLine && (endLineMatched = matchedEndLine(start, bidi.status, bidi.context, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLine, endLineYPos))) {
1581 break;
1582 }
1583 lineCount++;
1584 betweenMidpoints = false;
1585 isLineEmpty = true;
1586 pagebreakHint = false;
1587 if (pagedMode) {
1588 oldPos = m_height;
1589 oldStart = start;
1590 oldBidi = bidi;
1591 }
1592 if (lineCount == breakBeforeLine) {
1593 m_height = pageTopAfter(oldPos);
1594 pagebreakHint = true;
1595 }
1596 redo_linebreak:
1597 end = findNextLineBreak(start, bidi);
1598 if (start.atEnd()) {
1599 deleteBidiRuns(renderArena());
1600 break;
1601 }
1602 if (!isLineEmpty) {
1603 bidiReorderLine(start, end, bidi);
1604
1605 // Now that the runs have been ordered, we create the line boxes.
1606 // At the same time we figure out where border/padding/margin should be applied for
1607 // inline flow boxes.
1608
1609 RootInlineBox *lineBox = nullptr;
1610 if (sBidiRunCount) {
1611 lineBox = constructLine(start, end);
1612 if (lineBox) {
1613 lineBox->setEndsWithBreak(previousLineBrokeAtBR);
1614 if (pagebreakHint) {
1615 lineBox->setAfterPageBreak(true);
1616 }
1617
1618 // Now we position all of our text runs horizontally.
1619 computeHorizontalPositionsForLine(lineBox, bidi);
1620
1621 // Now position our text runs vertically.
1622 computeVerticalPositionsForLine(lineBox);
1623
1624 // SVG
1625 if (lineBox->isSVGRootInlineBox()) {
1626 //qCDebug(KHTML_LOG) << "svgrootinline box:";
1627 WebCore::SVGRootInlineBox *svgLineBox = static_cast<WebCore::SVGRootInlineBox *>(lineBox);
1628 svgLineBox->computePerCharacterLayoutInformation();
1629 }
1630
1631 deleteBidiRuns(renderArena());
1632
1633 if (lineBox->afterPageBreak() && hasFloats() && !pagebreakHint) {
1634 start = end = oldStart;
1635 bidi = oldBidi;
1636 m_height = pageTopAfter(oldPos);
1637 deleteLastLineBox(renderArena());
1638 pagebreakHint = true;
1639 goto redo_linebreak;
1640 }
1641 }
1642 }
1643
1644 if (end == start || (end.obj && end.obj->isBR() && !start.obj->isBR())) {
1645 end.increment(&bidi);
1646 } else if (end.obj && end.obj->style()->preserveLF() && end.current() == QChar('\n')) {
1647 end.increment(&bidi);
1648 }
1649
1650 if (lineBox) {
1651 lineBox->setLineBreakInfo(end.obj, end.pos, bidi.status, bidi.context);
1652 }
1653
1654 m_firstLine = false;
1655 newLine();
1656 }
1657
1658 sNumMidpoints = 0;
1659 sCurrMidpoint = 0;
1660 sCompactFirstBidiRun = sCompactLastBidiRun = nullptr;
1661 sCompactBidiRunCount = 0;
1662 }
1663 startEmbed->deref();
1664 //embed->deref();
1665
1666 if (endLine) {
1667 if (endLineMatched) {
1668 // Attach all the remaining lines, and then adjust their y-positions as needed.
1669 for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) {
1670 line->attachLine();
1671 }
1672
1673 // Now apply the offset to each line if needed.
1674 int delta = m_height - endLineYPos;
1675 if (delta) {
1676 for (RootInlineBox *line = endLine; line; line = line->nextRootBox()) {
1677 line->adjustPosition(0, delta);
1678 }
1679 }
1680 m_height = lastRootBox()->blockHeight();
1681 } else {
1682 // Delete all the remaining lines.
1683 InlineRunBox *line = endLine;
1684 RenderArena *arena = renderArena();
1685 while (line) {
1686 InlineRunBox *next = line->nextLineBox();
1687 line->deleteLine(arena);
1688 line = next;
1689 }
1690 }
1691 }
1692 }
1693
1694 sNumMidpoints = 0;
1695 sCurrMidpoint = 0;
1696
1697 // If we violate widows page-breaking rules, we set a hint and relayout.
1698 // Note that the widows rule might still be violated afterwards if the lines have become wider
1699 if (canvas()->pagedMode() && containsPageBreak() && breakBeforeLine == 0) {
1700 int orphans = 0;
1701 int widows = 0;
1702 // find breaking line
1703 InlineRunBox *lineBox = firstLineBox();
1704 while (lineBox) {
1705 if (lineBox->isInlineFlowBox()) {
1706 InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(lineBox);
1707 if (flowBox->afterPageBreak()) {
1708 break;
1709 }
1710 }
1711 orphans++;
1712 lineBox = lineBox->nextLineBox();
1713 }
1714 InlineFlowBox *pageBreaker = static_cast<InlineFlowBox *>(lineBox);
1715 if (!pageBreaker) {
1716 goto no_break;
1717 }
1718 // count widows
1719 while (lineBox && widows < style()->widows()) {
1720 if (lineBox->hasTextChildren()) {
1721 widows++;
1722 }
1723 lineBox = lineBox->nextLineBox();
1724 }
1725 // Widows rule broken and more orphans left to use
1726 if (widows < style()->widows() && orphans > 0) {
1727 // qCDebug(KHTML_LOG) << "Widows: " << widows;
1728 // Check if we have enough orphans after respecting widows count
1729 int newOrphans = orphans - (style()->widows() - widows);
1730 if (newOrphans < style()->orphans()) {
1731 if (parent()->canClear(this, PageBreakHarder)) {
1732 // Relayout to remove incorrect page-break
1733 setNeedsPageClear(true);
1734 setContainsPageBreak(false);
1735 layoutInlineChildren(relayoutChildren, -1);
1736 return;
1737 }
1738 } else {
1739 // Set hint and try again
1740 layoutInlineChildren(relayoutChildren, newOrphans + 1);
1741 return;
1742 }
1743 }
1744 }
1745 no_break:
1746
1747 // in case we have a float on the last line, it might not be positioned up to now.
1748 // This has to be done before adding in the bottom border/padding, or the float will
1749 // include the padding incorrectly. -dwh
1750 positionNewFloats();
1751
1752 // Now add in the bottom border/padding.
1753 m_height += toAdd;
1754
1755 // Always make sure this is at least our height.
1756 m_overflowHeight = qMax(m_height, m_overflowHeight);
1757
1758 // See if any lines spill out of the block. If so, we need to update our overflow width.
1759 checkLinesForOverflow();
1760
1761 // See if we have any lines that spill out of our block. If we do, then we will
1762 // possibly need to truncate text.
1763 if (hasTextOverflow) {
1764 checkLinesForTextOverflow();
1765 }
1766
1767 #if BIDI_DEBUG > 1
1768 qCDebug(KHTML_LOG) << " ------- bidi end " << this << " -------";
1769 #endif
1770 //qCDebug(KHTML_LOG) << "RenderBlock::layoutInlineChildren time used " << qt.elapsed();
1771 //qCDebug(KHTML_LOG) << "height = " << m_height;
1772 }
1773
determineStartPosition(bool fullLayout,BidiIterator & start,BidiState & bidi)1774 RootInlineBox *RenderBlock::determineStartPosition(bool fullLayout, BidiIterator &start, BidiState &bidi)
1775 {
1776 RootInlineBox *curr = nullptr;
1777 RootInlineBox *last = nullptr;
1778 RenderObject *startObj = nullptr;
1779 int pos = 0;
1780
1781 if (fullLayout) {
1782 // Nuke all our lines.
1783 // ### should be done already at this point... assert( !firstRootBox() )
1784 if (firstRootBox()) {
1785 RenderArena *arena = renderArena();
1786 curr = firstRootBox();
1787 while (curr) {
1788 RootInlineBox *next = curr->nextRootBox();
1789 curr->deleteLine(arena);
1790 curr = next;
1791 }
1792 assert(!firstLineBox() && !lastLineBox());
1793 }
1794 } else {
1795 int cnt = 0;
1796 for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) {
1797 cnt++;
1798 }
1799 if (curr) {
1800 // qCDebug(KHTML_LOG) << "found dirty line at " << cnt;
1801 // We have a dirty line.
1802 if (RootInlineBox *prevRootBox = curr->prevRootBox()) {
1803 // We have a previous line.
1804 if (!prevRootBox->endsWithBreak() || (prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= static_cast<RenderText *>(prevRootBox->lineBreakObj())->stringLength()))
1805 // The previous line didn't break cleanly or broke at a newline
1806 // that has been deleted, so treat it as dirty too.
1807 {
1808 curr = prevRootBox;
1809 }
1810 }
1811 } else {
1812 // qCDebug(KHTML_LOG) << "No dirty line found";
1813 // No dirty lines were found.
1814 // If the last line didn't break cleanly, treat it as dirty.
1815 if (lastRootBox() && !lastRootBox()->endsWithBreak()) {
1816 curr = lastRootBox();
1817 }
1818 }
1819
1820 // If we have no dirty lines, then last is just the last root box.
1821 last = curr ? curr->prevRootBox() : lastRootBox();
1822 }
1823
1824 m_firstLine = !last;
1825 previousLineBrokeAtBR = !last || last->endsWithBreak();
1826 if (last) {
1827 m_height = last->blockHeight();
1828 startObj = last->lineBreakObj();
1829 pos = last->lineBreakPos();
1830 bidi.status = last->lineBreakBidiStatus();
1831 } else {
1832 startObj = first(this, &bidi, false);
1833 }
1834
1835 start = BidiIterator(this, startObj, pos);
1836
1837 return curr;
1838 }
1839
determineEndPosition(RootInlineBox * startLine,BidiIterator & cleanLineStart,BidiStatus & cleanLineBidiStatus,BidiContext * cleanLineBidiContext,int & yPos)1840 RootInlineBox *RenderBlock::determineEndPosition(RootInlineBox *startLine, BidiIterator &cleanLineStart, BidiStatus &cleanLineBidiStatus, BidiContext *cleanLineBidiContext, int &yPos)
1841 {
1842 RootInlineBox *last = nullptr;
1843 if (!startLine) {
1844 last = nullptr;
1845 } else {
1846 for (RootInlineBox *curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) {
1847 if (curr->isDirty()) {
1848 last = nullptr;
1849 } else if (!last) {
1850 last = curr;
1851 }
1852 }
1853 }
1854
1855 if (!last) {
1856 return nullptr;
1857 }
1858
1859 RootInlineBox *prev = last->prevRootBox();
1860 cleanLineStart = BidiIterator(this, prev->lineBreakObj(), prev->lineBreakPos());
1861 cleanLineBidiStatus = prev->lineBreakBidiStatus();
1862 cleanLineBidiContext = prev->lineBreakBidiContext();
1863 yPos = prev->blockHeight();
1864
1865 return last;
1866 }
1867
matchedEndLine(const BidiIterator & start,const BidiStatus & status,BidiContext * context,const BidiIterator & endLineStart,const BidiStatus & endLineStatus,BidiContext * endLineContext,RootInlineBox * & endLine,int & endYPos)1868 bool RenderBlock::matchedEndLine(const BidiIterator &start, const BidiStatus &status, BidiContext *context,
1869 const BidiIterator &endLineStart, const BidiStatus &endLineStatus, BidiContext *endLineContext,
1870 RootInlineBox *&endLine, int &endYPos)
1871 {
1872 if (start == endLineStart) {
1873 return status == endLineStatus && endLineContext && (*context == *endLineContext);
1874 } else {
1875 // The first clean line doesn't match, but we can check a handful of following lines to try
1876 // to match back up.
1877 static int numLines = 8; // The # of lines we're willing to match against.
1878 RootInlineBox *line = endLine;
1879 for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) {
1880 if (line->lineBreakObj() == start.obj && line->lineBreakPos() == start.pos) {
1881 // We have a match.
1882 if ((line->lineBreakBidiStatus() != status) || (line->lineBreakBidiContext() != context)) {
1883 return false; // ...but the bidi state doesn't match.
1884 }
1885 RootInlineBox *result = line->nextRootBox();
1886
1887 // Set our yPos to be the block height of endLine.
1888 if (result) {
1889 endYPos = line->blockHeight();
1890 }
1891
1892 // Now delete the lines that we failed to sync.
1893 RootInlineBox *boxToDelete = endLine;
1894 RenderArena *arena = renderArena();
1895 while (boxToDelete && boxToDelete != result) {
1896 RootInlineBox *next = boxToDelete->nextRootBox();
1897 boxToDelete->deleteLine(arena);
1898 boxToDelete = next;
1899 }
1900
1901 endLine = result;
1902 return result;
1903 }
1904 }
1905 }
1906 return false;
1907 }
1908
setStaticPosition(RenderBlock * p,RenderObject * o,bool * needToSetStaticX=nullptr,bool * needToSetStaticY=nullptr)1909 static void setStaticPosition(RenderBlock *p, RenderObject *o, bool *needToSetStaticX = nullptr, bool *needToSetStaticY = nullptr)
1910 {
1911 // If our original display wasn't an inline type, then we can
1912 // determine our static x position now.
1913 bool nssx, nssy;
1914 bool isInlineType = o->style()->isOriginalDisplayInlineType();
1915 nssx = o->hasStaticX();
1916 if (nssx && o->isBox()) {
1917 static_cast<RenderBox *>(o)->setStaticX(o->parent()->style()->direction() == LTR ?
1918 p->borderLeft() + p->paddingLeft() :
1919 p->borderRight() + p->paddingRight());
1920 nssx = isInlineType;
1921 }
1922
1923 // If our original display was an INLINE type, then we can
1924 // determine our static y position now.
1925 nssy = o->hasStaticY();
1926 if (nssy && o->isBox()) {
1927 static_cast<RenderBox *>(o)->setStaticY(p->height());
1928 nssy = !isInlineType;
1929 }
1930 if (needToSetStaticX) {
1931 *needToSetStaticX = nssx;
1932 }
1933 if (needToSetStaticY) {
1934 *needToSetStaticY = nssy;
1935 }
1936 }
1937
requiresLineBox(BidiIterator & it)1938 static inline bool requiresLineBox(BidiIterator &it)
1939 {
1940 if (it.obj->isFloatingOrPositioned()) {
1941 return false;
1942 }
1943 if (it.obj->isInlineFlow()) {
1944 return (getBorderPaddingMargin(it.obj, it.endOfInline) != 0);
1945 }
1946 if (it.obj->isText() && !static_cast<RenderText *>(it.obj)->length()) {
1947 return false;
1948 }
1949 if (it.obj->style()->preserveWS() || it.obj->isBR()) {
1950 return true;
1951 }
1952
1953 switch (it.current().unicode()) {
1954 case 0x0009: // ASCII tab
1955 case 0x000A: // ASCII line feed
1956 case 0x000C: // ASCII form feed
1957 case 0x0020: // ASCII space
1958 case 0x200B: // Zero-width space
1959 return false;
1960 }
1961 return true;
1962 }
1963
inlineChildNeedsLineBox(RenderObject * inlineObj)1964 bool RenderBlock::inlineChildNeedsLineBox(RenderObject *inlineObj) // WC: generatesLineBoxesForInlineChild
1965 {
1966 assert(inlineObj->parent() == this);
1967
1968 BidiIterator it(this, inlineObj, 0);
1969 while (!it.atEnd() && !requiresLineBox(it)) {
1970 it.increment(nullptr, false /*skipInlines*/);
1971 }
1972
1973 return !it.atEnd();
1974 }
1975
fitBelowFloats(int widthToFit,int & availableWidth)1976 void RenderBlock::fitBelowFloats(int widthToFit, int &availableWidth)
1977 {
1978 assert(widthToFit > availableWidth);
1979
1980 int floatBottom;
1981 int lastFloatBottom = m_height;
1982 int newLineWidth = availableWidth;
1983 while (true) {
1984 floatBottom = nearestFloatBottom(lastFloatBottom);
1985 if (!floatBottom) {
1986 break;
1987 }
1988
1989 newLineWidth = lineWidth(floatBottom);
1990 lastFloatBottom = floatBottom;
1991 if (newLineWidth >= widthToFit) {
1992 break;
1993 }
1994 }
1995 if (newLineWidth > availableWidth) {
1996 m_height = lastFloatBottom;
1997 availableWidth = newLineWidth;
1998 #ifdef DEBUG_LINEBREAKS
1999 qCDebug(KHTML_LOG) << " new position at " << m_height << " newWidth " << availableWidth;
2000 #endif
2001 }
2002 }
2003
findNextLineBreak(BidiIterator & start,BidiState & bidi)2004 BidiIterator RenderBlock::findNextLineBreak(BidiIterator &start, BidiState &bidi)
2005 {
2006 int width = lineWidth(m_height);
2007 int w = 0; // the width from the start of the line up to the currently chosen breaking opportunity
2008 int tmpW = 0; // the accumulated width since the last chosen breaking opportunity
2009 #ifdef DEBUG_LINEBREAKS
2010 qCDebug(KHTML_LOG) << "findNextLineBreak: line at " << m_height << " line width " << width;
2011 qCDebug(KHTML_LOG) << "sol: " << start.obj << " " << start.pos;
2012 #endif
2013
2014 BidiIterator posStart = start;
2015 bool hadPosStart = false;
2016
2017 // Skip initial whitespace
2018 while (!start.atEnd() && !requiresLineBox(start)) {
2019 if (start.obj->isFloating() || start.obj->isPosWithStaticDim()) {
2020 RenderObject *o = start.obj;
2021 // add to special objects...
2022 if (o->isFloating()) {
2023 insertFloatingObject(o);
2024 positionNewFloats();
2025 width = lineWidth(m_height);
2026 } else if (o->isPositioned()) {
2027 // add midpoints to have positioned objects at the correct static location
2028 // while still skipping initial whitespace.
2029 if (!hadPosStart) {
2030 hadPosStart = true;
2031 posStart = start;
2032 // include this object then stop
2033 addMidpoint(BidiIterator(nullptr, o, 0));
2034 } else {
2035 // start/stop
2036 addMidpoint(BidiIterator(nullptr, o, 0));
2037 addMidpoint(BidiIterator(nullptr, o, 0));
2038 }
2039 setStaticPosition(this, o);
2040 }
2041 }
2042 start.increment(&bidi, false /*skipInlines*/);
2043 }
2044
2045 if (hadPosStart && !start.atEnd()) {
2046 addMidpoint(start);
2047 }
2048
2049 if (start.atEnd()) {
2050 if (hadPosStart) {
2051 start = posStart;
2052 posStart.increment();
2053 return posStart;
2054 }
2055 return start;
2056 }
2057
2058 // This variable says we have encountered an object after which initial whitespace should be ignored (e.g. InlineFlows at the beginning of a line).
2059 // Either we have nothing to do, if there is no whitespace after the object... or we have to enter the ignoringSpaces state.
2060 // This dilemma will be resolved when we have a peek at the next object.
2061 bool checkShouldIgnoreInitialWhitespace = false;
2062
2063 // This variable is used only if whitespace isn't set to PRE, and it tells us whether
2064 // or not we are currently ignoring whitespace.
2065 bool ignoringSpaces = false;
2066 BidiIterator ignoreStart;
2067
2068 // This variable tracks whether the very last character we saw was a space. We use
2069 // this to detect when we encounter a second space so we know we have to terminate
2070 // a run.
2071 bool currentCharacterIsSpace = false;
2072
2073 // This variable tracks whether there is space still available on the line for floating objects.
2074 // Once a floating object does not fit, we wait till next linebreak before positioning more floats.
2075 bool floatsFitOnLine = true;
2076
2077 RenderObject *trailingSpaceObject = nullptr;
2078
2079 BidiIterator lBreak = start;
2080 InlineMinMaxIterator it(start.par, start.obj, start.endOfInline, false /*skipPositioned*/);
2081 InlineMinMaxIterator lastIt = it;
2082 int pos = start.pos;
2083
2084 bool prevLineBrokeCleanly = previousLineBrokeAtBR;
2085 previousLineBrokeAtBR = false;
2086
2087 RenderObject *o = it.current;
2088 while (o) {
2089 #ifdef DEBUG_LINEBREAKS
2090 qCDebug(KHTML_LOG) << "new object " << o << " width = " << w << " tmpw = " << tmpW;
2091 #endif
2092 if (o->isBR()) {
2093 if (w + tmpW <= width) {
2094 lBreak.obj = o;
2095 lBreak.pos = 0;
2096 lBreak.endOfInline = it.endOfInline;
2097
2098 // A <br> always breaks a line, so don't let the line be collapsed
2099 // away. Also, the space at the end of a line with a <br> does not
2100 // get collapsed away. It only does this if the previous line broke
2101 // cleanly. Otherwise the <br> has no effect on whether the line is
2102 // empty or not.
2103 if (prevLineBrokeCleanly) {
2104 isLineEmpty = false;
2105 }
2106 trailingSpaceObject = nullptr;
2107 previousLineBrokeAtBR = true;
2108
2109 if (!isLineEmpty) {
2110 // only check the clear status for non-empty lines.
2111 EClear clear = o->style()->clear();
2112 if (clear != CNONE) {
2113 m_clearStatus = (EClear)(m_clearStatus | clear);
2114 }
2115 }
2116 }
2117 goto end;
2118 }
2119 if (o->isFloatingOrPositioned()) {
2120 // add to special objects...
2121 if (o->isFloating()) {
2122 insertFloatingObject(o);
2123 // check if it fits in the current line.
2124 // If it does, position it now, otherwise, position
2125 // it after moving to next line (in newLine() func)
2126 if (floatsFitOnLine && o->width() + o->marginLeft() + o->marginRight() + w + tmpW <= width) {
2127 positionNewFloats();
2128 width = lineWidth(m_height);
2129 } else {
2130 floatsFitOnLine = false;
2131 }
2132 } else if (o->isPositioned() && o->isPosWithStaticDim()) {
2133 bool needToSetStaticX;
2134 bool needToSetStaticY;
2135 setStaticPosition(this, o, &needToSetStaticX, &needToSetStaticY);
2136
2137 // If we're ignoring spaces, we have to stop and include this object and
2138 // then start ignoring spaces again.
2139 if (needToSetStaticX || needToSetStaticY) {
2140 trailingSpaceObject = nullptr;
2141 ignoreStart.obj = o;
2142 ignoreStart.pos = 0;
2143 if (ignoringSpaces) {
2144 addMidpoint(ignoreStart); // Stop ignoring spaces.
2145 addMidpoint(ignoreStart); // Start ignoring again.
2146 }
2147 }
2148 }
2149 } else if (o->isInlineFlow()) {
2150 tmpW += getBorderPaddingMargin(o, it.endOfInline);
2151 if (isLineEmpty) {
2152 isLineEmpty = !tmpW;
2153 }
2154 if (o->isWordBreak()) { // #### shouldn't be an InlineFlow!
2155 w += tmpW;
2156 tmpW = 0;
2157 lBreak.obj = o;
2158 lBreak.pos = 0;
2159 lBreak.endOfInline = it.endOfInline;
2160 } else if (!it.endOfInline) {
2161 // this is the beginning of the line (other non-initial inline flows are handled directly when
2162 // incrementing the iterator below). We want to skip initial whitespace as much as possible.
2163 checkShouldIgnoreInitialWhitespace = true;
2164 }
2165 } else if (o->isReplaced() || o->isGlyph()) {
2166 EWhiteSpace currWS = o->style()->whiteSpace();
2167 EWhiteSpace lastWS = lastIt.current->style()->whiteSpace();
2168
2169 // WinIE marquees have different whitespace characteristics by default when viewed from
2170 // the outside vs. the inside. Text inside is NOWRAP, and so we altered the marquee's
2171 // style to reflect this, but we now have to get back to the original whitespace value
2172 // for the marquee when checking for line breaking.
2173 if (o->isHTMLMarquee() && o->layer() && o->layer()->marquee()) {
2174 currWS = o->layer()->marquee()->whiteSpace();
2175 }
2176 if (lastIt.current->isHTMLMarquee() && lastIt.current->layer() && lastIt.current->layer()->marquee()) {
2177 lastWS = lastIt.current->layer()->marquee()->whiteSpace();
2178 }
2179
2180 // Break on replaced elements if either has normal white-space.
2181 if (currWS == NORMAL || lastWS == NORMAL) {
2182 w += tmpW;
2183 tmpW = 0;
2184 lBreak.obj = o;
2185 lBreak.pos = 0;
2186 lBreak.endOfInline = false;
2187 }
2188
2189 tmpW += o->width() + o->marginLeft() + o->marginRight();
2190 if (ignoringSpaces) {
2191 BidiIterator startMid(nullptr, o, 0);
2192 addMidpoint(startMid);
2193 }
2194 isLineEmpty = false;
2195 ignoringSpaces = false;
2196 currentCharacterIsSpace = false;
2197 trailingSpaceObject = nullptr;
2198
2199 if (o->isListMarker()) {
2200 checkShouldIgnoreInitialWhitespace = true;
2201 }
2202 } else if (o->isText()) {
2203 RenderText *t = static_cast<RenderText *>(o);
2204 int strlen = t->stringLength();
2205 int len = strlen - pos;
2206 QChar *str = t->text();
2207
2208 const Font *f = t->htmlFont(m_firstLine);
2209 // proportional font, needs a bit more work.
2210 int lastSpace = pos;
2211 bool autoWrap = o->style()->autoWrap();
2212 bool preserveWS = o->style()->preserveWS();
2213 bool preserveLF = o->style()->preserveLF();
2214 #ifdef APPLE_CHANGES
2215 int wordSpacing = o->style()->wordSpacing();
2216 #endif
2217 bool nextIsSoftBreakable = false;
2218 bool checkBreakWord = autoWrap && (o->style()->wordWrap() == WWBREAKWORD);
2219
2220 while (len) {
2221 bool previousCharacterIsSpace = currentCharacterIsSpace;
2222 bool isSoftBreakable = nextIsSoftBreakable;
2223 nextIsSoftBreakable = false;
2224 const QChar c = str[pos];
2225 currentCharacterIsSpace = c.unicode() == ' ';
2226 checkBreakWord &= !w; // only break words when no other breaking opportunity exists earlier
2227 // on the line (even within the text object we are currently processing)
2228
2229 if (preserveWS || !currentCharacterIsSpace) {
2230 isLineEmpty = false;
2231 }
2232
2233 // Check for soft hyphens. Go ahead and ignore them.
2234 if (c.unicode() == SOFT_HYPHEN && pos > 0) {
2235 nextIsSoftBreakable = true;
2236 if (!ignoringSpaces) {
2237 // Ignore soft hyphens
2238 BidiIterator endMid(nullptr, o, pos - 1);
2239 addMidpoint(endMid);
2240
2241 // Add the width up to but not including the hyphen.
2242 tmpW += t->width(lastSpace, pos - lastSpace, f);
2243
2244 // For wrapping text only, include the hyphen. We need to ensure it will fit
2245 // on the line if it shows when we break.
2246 if (o->style()->autoWrap()) {
2247 const QChar softHyphen(0x00ad);
2248 tmpW += f->charWidth(&softHyphen, 1, 0, true);
2249 }
2250
2251 BidiIterator startMid(nullptr, o, pos + 1);
2252 addMidpoint(startMid);
2253 }
2254
2255 pos++;
2256 len--;
2257 lastSpace = pos; // Cheesy hack to prevent adding in widths of the run twice.
2258 continue;
2259 }
2260 #ifdef APPLE_CHANGES // KDE applies wordspacing differently
2261 bool applyWordSpacing = false;
2262 #endif
2263 if (ignoringSpaces) {
2264 // We need to stop ignoring spaces, if we encounter a non-space or
2265 // a run that doesn't collapse spaces.
2266 if (!currentCharacterIsSpace || preserveWS) {
2267 // Stop ignoring spaces and begin at this
2268 // new point.
2269 ignoringSpaces = false;
2270 lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces.
2271 BidiIterator startMid(nullptr, o, pos);
2272 addMidpoint(startMid);
2273 } else {
2274 // Just keep ignoring these spaces.
2275 pos++;
2276 len--;
2277 continue;
2278 }
2279 }
2280 bool isbreakablePosition = (preserveLF && c.unicode() == '\n') || (autoWrap && (isBreakable(str, pos, strlen) || isSoftBreakable));
2281 if (isbreakablePosition || checkBreakWord) {
2282 tmpW += t->width(lastSpace, pos - lastSpace, f);
2283 #ifdef APPLE_CHANGES
2284 applyWordSpacing = (wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace &&
2285 !t->containsOnlyWhitespace(pos + 1, strlen - (pos + 1)));
2286 #endif
2287 #ifdef DEBUG_LINEBREAKS
2288 qCDebug(KHTML_LOG) << "found space at " << pos << " in string '" << QString(str, strlen).toLatin1().constData() << "' adding " << tmpW << " new width = " << w;
2289 #endif
2290 if (!w && autoWrap && tmpW > width) {
2291 fitBelowFloats(tmpW, width);
2292 }
2293
2294 if (autoWrap) {
2295 if (w + tmpW > width) {
2296 if (checkBreakWord && pos) {
2297 lBreak.obj = o;
2298 lBreak.pos = pos - 1;
2299 lBreak.endOfInline = false;
2300 }
2301 goto end;
2302 } else if ((pos > 1 && str[pos - 1].unicode() == SOFT_HYPHEN))
2303 // Subtract the width of the soft hyphen out since we fit on a line.
2304 {
2305 tmpW -= t->width(pos - 1, 1, f);
2306 }
2307 }
2308
2309 if (preserveLF && (str + pos)->unicode() == '\n') {
2310 lBreak.obj = o;
2311 lBreak.pos = pos;
2312 lBreak.endOfInline = false;
2313
2314 #ifdef DEBUG_LINEBREAKS
2315 qCDebug(KHTML_LOG) << "forced break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w;
2316 #endif
2317 return lBreak;
2318 }
2319
2320 if (autoWrap && isbreakablePosition) {
2321 w += tmpW;
2322 tmpW = 0;
2323 lBreak.obj = o;
2324 lBreak.pos = pos;
2325 lBreak.endOfInline = false;
2326 }
2327
2328 lastSpace = pos;
2329 #ifdef APPLE_CHANGES
2330 if (applyWordSpacing) {
2331 w += wordSpacing;
2332 }
2333 #endif
2334 }
2335
2336 if (!ignoringSpaces && !preserveWS) {
2337 // If we encounter a second space, we need to go ahead and break up this run
2338 // and enter a mode where we start collapsing spaces.
2339 if (currentCharacterIsSpace && previousCharacterIsSpace) {
2340 ignoringSpaces = true;
2341
2342 // We just entered a mode where we are ignoring
2343 // spaces. Create a midpoint to terminate the run
2344 // before the second space.
2345 addMidpoint(ignoreStart);
2346 lastSpace = pos;
2347 }
2348 }
2349
2350 if (currentCharacterIsSpace && !previousCharacterIsSpace) {
2351 ignoreStart.obj = o;
2352 ignoreStart.pos = pos;
2353 }
2354
2355 if (!preserveWS && currentCharacterIsSpace && !ignoringSpaces) {
2356 trailingSpaceObject = o;
2357 } else if (preserveWS || !currentCharacterIsSpace) {
2358 trailingSpaceObject = nullptr;
2359 }
2360
2361 pos++;
2362 len--;
2363 }
2364
2365 if (!ignoringSpaces) {
2366 // We didn't find any space that would be beyond the line |width|.
2367 // Lets add to |tmpW| the remaining width since the last space we found.
2368 // Before we test this new |tmpW| however, we will have to look ahead to check
2369 // if the next object/position can serve as a line breaking opportunity.
2370 tmpW += t->width(lastSpace, pos - lastSpace, f);
2371 if (checkBreakWord && !w && pos && tmpW > width) {
2372 // Avoid doing the costly lookahead for break-word,
2373 // since we know we are allowed to break.
2374 lBreak.obj = o;
2375 lBreak.pos = pos - 1;
2376 lBreak.endOfInline = false;
2377 goto end;
2378 }
2379 }
2380 } else {
2381 KHTMLAssert(false);
2382 }
2383
2384 InlineMinMaxIterator savedIt = lastIt;
2385 lastIt = it;
2386 o = it.next();
2387
2388 // Advance the iterator to the next non-inline-flow
2389 while (o && o->isInlineFlow() && !o->isWordBreak()) {
2390 tmpW += getBorderPaddingMargin(o, it.endOfInline);
2391 if (isLineEmpty) {
2392 isLineEmpty = !tmpW;
2393 }
2394 o = it.next();
2395 }
2396
2397 // All code below, until the end of the loop, is looking ahead the |it| object we just
2398 // advanced to, comparing it to the previous object |lastIt|.
2399
2400 if (checkShouldIgnoreInitialWhitespace) {
2401 // Check if we should switch to ignoringSpaces state
2402 if (!style()->preserveWS() && it.current && it.current->isText()) {
2403 const RenderText *rt = static_cast<RenderText *>(it.current);
2404 if (rt->stringLength() > 0 && (rt->text()[0].category() == QChar::Separator_Space || rt->text()[0] == '\n')) {
2405 currentCharacterIsSpace = true;
2406 ignoringSpaces = true;
2407 BidiIterator endMid(nullptr, lastIt.current, 0);
2408 addMidpoint(endMid);
2409 }
2410 }
2411 checkShouldIgnoreInitialWhitespace = false;
2412 }
2413
2414 bool autoWrap = lastIt.current->style()->autoWrap();
2415 bool canBreak = !lBreak.obj || !lBreak.obj->isInlineFlow() || !lBreak.obj->firstChild();
2416
2417 bool checkForBreak = autoWrap;
2418 if (canBreak) {
2419 if (!autoWrap && w && w + tmpW > width && lBreak.obj && !lastIt.current->style()->preserveLF())
2420 // ### needs explanation
2421 {
2422 checkForBreak = true;
2423 } else if (it.current && lastIt.current->isText() && it.current->isText() && !it.current->isBR()) {
2424 // We are looking ahead the next text object to see if it continues a word started previously,
2425 // or is a line-breaking opportunity.
2426 if (autoWrap || it.current->style()->autoWrap()) {
2427 if (currentCharacterIsSpace)
2428 // "<i>s </i>top"
2429 // _ ^
2430 {
2431 checkForBreak = true;
2432 } else {
2433 // either "<i>c</i>ontinue" or "<i>s</i> top"
2434 // _ ^ _ ^
2435 checkForBreak = false;
2436 RenderText *nextText = static_cast<RenderText *>(it.current);
2437 if (nextText->stringLength() != 0) {
2438 QChar c = nextText->text()[0];
2439 // If the next item is a space, then we may try to break.
2440 // Otherwise the next text run continues our word (and so it needs to
2441 // keep adding to |tmpW|).
2442 if (c == ' ' || c == '\t' || (c == '\n' && !it.current->style()->preserveLF())) {
2443 checkForBreak = true;
2444 }
2445 }
2446
2447 bool willFitOnLine = (w + tmpW <= width);
2448 if (!willFitOnLine && !w) {
2449 fitBelowFloats(tmpW, width);
2450 willFitOnLine = tmpW <= width;
2451 }
2452 bool canPlaceOnLine = willFitOnLine || !autoWrap;
2453 if (canPlaceOnLine && checkForBreak) {
2454 w += tmpW;
2455 tmpW = 0;
2456 lBreak.obj = it.current;
2457 lBreak.pos = 0;
2458 lBreak.endOfInline = it.endOfInline;
2459 }
2460 }
2461 }
2462 }
2463
2464 if (checkForBreak && (w + tmpW > width)) {
2465 // if we have floats, try to get below them.
2466 if (currentCharacterIsSpace && !ignoringSpaces && !lastIt.current->style()->preserveWS()) {
2467 trailingSpaceObject = nullptr;
2468 }
2469
2470 if (w) {
2471 goto end;
2472 }
2473 fitBelowFloats(tmpW, width);
2474
2475 // |width| may have been adjusted because we got shoved down past a float (thus
2476 // giving us more room), so we need to retest, and only jump to
2477 // the end label if we still don't fit on the line. -dwh
2478 if (w + tmpW > width) {
2479 it = lastIt;
2480 lastIt = savedIt;
2481 o = it.current;
2482 goto end;
2483 }
2484 }
2485 }
2486 if (!lastIt.current->isFloatingOrPositioned() && lastIt.current->isReplaced() && lastIt.current->style()->autoWrap()) {
2487 // Go ahead and add in tmpW.
2488 w += tmpW;
2489 tmpW = 0;
2490 lBreak.obj = o;
2491 lBreak.pos = 0;
2492 lBreak.endOfInline = it.endOfInline;
2493 }
2494
2495 // Clear out our character space bool, since inline <pre>s don't collapse whitespace
2496 // with adjacent inline normal/nowrap spans.
2497 if (lastIt.current->style()->preserveWS()) {
2498 currentCharacterIsSpace = false;
2499 }
2500
2501 pos = 0;
2502 }
2503
2504 #ifdef DEBUG_LINEBREAKS
2505 qCDebug(KHTML_LOG) << "end of par, width = " << width << " linewidth = " << w + tmpW;
2506 #endif
2507 if (w + tmpW <= width || (lastIt.current && !lastIt.current->style()->autoWrap())) {
2508 lBreak.obj = nullptr;
2509 lBreak.pos = 0;
2510 lBreak.endOfInline = false;
2511 }
2512
2513 end:
2514 if (lBreak == start && !lBreak.obj->isBR()) {
2515 // Having an |lBreak| identical to our |start| at this point means the first suitable
2516 // break point |it.current| that we found was past |width|, so we jumped to the |end| label
2517 // before we could set this (overflowing) breaking opportunity. Let's set it now.
2518 if (style()->whiteSpace() == PRE) {
2519 // FIXME: Don't really understand this case.
2520 if (pos != 0) {
2521 lBreak.obj = o;
2522 lBreak.pos = pos - 1;
2523 lBreak.endOfInline = it.endOfInline;
2524 } else {
2525 lBreak.obj = lastIt.current;
2526 lBreak.pos = lastIt.current->isText() ? lastIt.current->length() : 0;
2527 lBreak.endOfInline = lastIt.endOfInline;
2528 }
2529 } else if (lBreak.obj) {
2530 lBreak.obj = o;
2531 lBreak.pos = (o && o->isText() ? pos : 0);
2532 lBreak.endOfInline = it.endOfInline;
2533 }
2534 }
2535
2536 if (hadPosStart) {
2537 start = posStart;
2538 }
2539
2540 if (lBreak == start) {
2541 // make sure we consume at least one char/object.
2542 lBreak.increment();
2543 }
2544
2545 #ifdef DEBUG_LINEBREAKS
2546 qCDebug(KHTML_LOG) << "regular break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w;
2547 #endif
2548
2549 // Sanity check our midpoints.
2550 checkMidpoints(lBreak);
2551
2552 if (trailingSpaceObject) {
2553 // This object is either going to be part of the last midpoint, or it is going
2554 // to be the actual endpoint. In both cases we just decrease our pos by 1 level to
2555 // exclude the space, allowing it to - in effect - collapse into the newline.
2556 if (sNumMidpoints % 2 == 1) {
2557 BidiIterator *midpoints = smidpoints->data();
2558 midpoints[sNumMidpoints - 1].pos--;
2559 }
2560 //else if (lBreak.pos > 0)
2561 // lBreak.pos--;
2562 else if (lBreak.obj == nullptr && trailingSpaceObject->isText()) {
2563 // Add a new end midpoint that stops right at the very end.
2564 RenderText *text = static_cast<RenderText *>(trailingSpaceObject);
2565 unsigned pos = text->length() >= 2 ? text->length() - 2 : UINT_MAX;
2566 BidiIterator endMid(nullptr, trailingSpaceObject, pos);
2567 addMidpoint(endMid);
2568 }
2569 }
2570
2571 // We might have made lBreak an iterator that points past the end
2572 // of the object. Do this adjustment to make it point to the start
2573 // of the next object instead to avoid confusing the rest of the
2574 // code.
2575 if (lBreak.pos > 0) {
2576 lBreak.pos--;
2577 lBreak.increment();
2578 }
2579
2580 if (lBreak.obj && lBreak.pos >= 2 && lBreak.obj->isText()) {
2581 // For soft hyphens on line breaks, we have to chop out the midpoints that made us
2582 // ignore the hyphen so that it will render at the end of the line.
2583 QChar c = static_cast<RenderText *>(lBreak.obj)->text()[lBreak.pos - 1];
2584 if (c.unicode() == SOFT_HYPHEN) {
2585 chopMidpointsAt(lBreak.obj, lBreak.pos - 2);
2586 }
2587 }
2588
2589 return lBreak;
2590 }
2591
checkLinesForOverflow()2592 void RenderBlock::checkLinesForOverflow()
2593 {
2594 for (RootInlineBox *curr = static_cast<khtml::RootInlineBox *>(firstLineBox()); curr; curr = static_cast<khtml::RootInlineBox *>(curr->nextLineBox())) {
2595 // m_overflowLeft = min(curr->leftOverflow(), m_overflowLeft);
2596 m_overflowTop = qMin(curr->topOverflow(), m_overflowTop);
2597 // m_overflowWidth = max(curr->rightOverflow(), m_overflowWidth);
2598 m_overflowHeight = qMax(curr->bottomOverflow(), m_overflowHeight);
2599 }
2600 }
2601
deleteEllipsisLineBoxes()2602 void RenderBlock::deleteEllipsisLineBoxes()
2603 {
2604 for (RootInlineBox *curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
2605 curr->clearTruncation();
2606 }
2607 }
2608
checkLinesForTextOverflow()2609 void RenderBlock::checkLinesForTextOverflow()
2610 {
2611 // Determine the width of the ellipsis using the current font.
2612 QChar ellipsis = 0x2026; // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if 0x2026 not renderable
2613 static QString ellipsisStr(ellipsis);
2614 const Font &firstLineFont = style(true)->htmlFont();
2615 const Font &font = style()->htmlFont();
2616 int firstLineEllipsisWidth = firstLineFont.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
2617 int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
2618
2619 // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see
2620 // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and
2621 // check the left edge of the line box to see if it is less
2622 // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()"
2623 bool ltr = style()->direction() == LTR;
2624 for (RootInlineBox *curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
2625 int blockEdge = ltr ? rightOffset(curr->yPos()) : leftOffset(curr->yPos());
2626 int lineBoxEdge = ltr ? curr->xPos() + curr->width() : curr->xPos();
2627 if ((ltr && lineBoxEdge > blockEdge) || (!ltr && lineBoxEdge < blockEdge)) {
2628 // This line spills out of our box in the appropriate direction. Now we need to see if the line
2629 // can be truncated. In order for truncation to be possible, the line must have sufficient space to
2630 // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis
2631 // space.
2632 int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth;
2633 if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) {
2634 curr->placeEllipsis(ellipsisStr, ltr, blockEdge, width);
2635 }
2636 }
2637 }
2638 }
2639
2640 // For --enable-final
2641 #undef BIDI_DEBUG
2642 #undef DEBUG_LINEBREAKS
2643 #undef DEBUG_LAYOUT
2644
2645 }
2646