KHtml

bidi.cpp
1 /**
2  * This file is part of the html renderer for KDE.
3  *
4  * Copyright (C) 2000-2003 Lars Knoll ([email protected])
5  * (C) 2003-2007 Apple Computer, Inc.
6  * (C) 2005 Allan Sandfeld Jensen ([email protected])
7  * (C) 2007-2009 Germain Garand ([email protected])
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 {
52  BidiIterator() : par(nullptr), obj(nullptr), pos(0), endOfInline(false) {}
53  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 &current() 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 {
69  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 
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 
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 
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 
146 void *BidiRun::operator new(size_t sz, RenderArena *renderArena) throw()
147 {
148  return renderArena->allocate(sz);
149 }
150 
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 
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 */
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 
196 BidiContext::~BidiContext()
197 {
198  if (parent) {
199  parent->deref();
200  }
201 }
202 
203 void BidiContext::ref() const
204 {
205  count++;
206 }
207 
208 void BidiContext::deref() const
209 {
210  count--;
211  if (count <= 0) {
212  delete this;
213  }
214 }
215 
216 // ---------------------------------------------------------------------
217 
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 
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 
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 
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 
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.
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 
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 
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 
364 inline bool BidiIterator::atEnd() const
365 {
366  if (!obj) {
367  return true;
368  }
369  return false;
370 }
371 
372 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 
388 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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
2602 void RenderBlock::deleteEllipsisLineBoxes()
2603 {
2604  for (RootInlineBox *curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
2605  curr->clearTruncation();
2606  }
2607 }
2608 
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 }
Separator_Space
This file is part of the HTML rendering engine for KDE.
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
QChar::Category category() const const
bool operator==(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
T * data()
void resize(int size)
const char * constData() const const
MESSAGECORE_EXPORT KMime::Content * firstChild(const KMime::Content *node)
ushort unicode() const const
QStringView level(QStringView ifopt)
const T & at(int i) const const
QByteArray toLatin1() const const
void start()
int size() const const
bool operator!=(const Qt3DRender::QGraphicsApiFilter &reference, const Qt3DRender::QGraphicsApiFilter &sample)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 22:48:11 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.