File indexing completed on 2025-04-27 10:07:22
0001 /** 0002 * This file is part of the html renderer for KDE. 0003 * 0004 * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) 0005 * (C) 2003-2007 Apple Computer, Inc. 0006 * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) 0007 * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org) 0008 * 0009 * This library is free software; you can redistribute it and/or 0010 * modify it under the terms of the GNU Library General Public 0011 * License as published by the Free Software Foundation; either 0012 * version 2 of the License, or (at your option) any later version. 0013 * 0014 * This library is distributed in the hope that it will be useful, 0015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0017 * Library General Public License for more details. 0018 * 0019 * You should have received a copy of the GNU Library General Public License 0020 * along with this library; see the file COPYING.LIB. If not, write to 0021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0022 * Boston, MA 02110-1301, USA. 0023 * 0024 */ 0025 #include "rendering/bidi.h" 0026 #include "rendering/break_lines.h" 0027 #include "rendering/render_block.h" 0028 #include "rendering/render_text.h" 0029 #include "rendering/render_arena.h" 0030 #include "rendering/render_layer.h" 0031 #include "rendering/render_canvas.h" 0032 #include "xml/dom_docimpl.h" 0033 #include <QVector> 0034 0035 #include "QDebug" 0036 0037 #include <limits.h> 0038 0039 // SVG 0040 #include "rendering/SVGRootInlineBox.h" 0041 #include "rendering/SVGInlineTextBox.h" 0042 0043 #define BIDI_DEBUG 0 0044 //#define DEBUG_LINEBREAKS 0045 //#define PAGE_DEBUG 0046 0047 namespace khtml 0048 { 0049 0050 // an iterator which goes through a BidiParagraph 0051 struct BidiIterator { 0052 BidiIterator() : par(nullptr), obj(nullptr), pos(0), endOfInline(false) {} 0053 BidiIterator(RenderBlock *_par, RenderObject *_obj, unsigned int _pos, bool eoi = false) : par(_par), obj(_obj), pos(_pos), endOfInline(eoi) {} 0054 0055 void increment(BidiState *bidi = nullptr, bool skipInlines = true); 0056 0057 bool atEnd() const; 0058 0059 const QChar ¤t() const; 0060 QChar::Direction direction() const; 0061 0062 RenderBlock *par; 0063 RenderObject *obj; 0064 unsigned int pos; 0065 bool endOfInline; 0066 }; 0067 0068 struct BidiState { 0069 BidiState() : context(nullptr) {} 0070 0071 BidiIterator sor; 0072 BidiIterator eor; 0073 BidiIterator last; 0074 BidiIterator current; 0075 BidiContext *context; 0076 BidiStatus status; 0077 }; 0078 0079 // Used to track a list of chained bidi runs. 0080 static BidiRun *sFirstBidiRun; 0081 static BidiRun *sLastBidiRun; 0082 static int sBidiRunCount; 0083 static BidiRun *sCompactFirstBidiRun; 0084 static BidiRun *sCompactLastBidiRun; 0085 static int sCompactBidiRunCount; 0086 static bool sBuildingCompactRuns; 0087 0088 // Midpoint globals. The goal is not to do any allocation when dealing with 0089 // these midpoints, so we just keep an array around and never clear it. We track 0090 // the number of items and position using the two other variables. 0091 static QVector<BidiIterator> *smidpoints; 0092 static uint sNumMidpoints; 0093 static uint sCurrMidpoint; 0094 static bool betweenMidpoints; 0095 0096 static bool isLineEmpty = true; 0097 static bool previousLineBrokeAtBR = false; 0098 static QChar::Direction dir = QChar::DirON; 0099 static bool emptyRun = true; 0100 static int numSpaces; 0101 0102 static void embed(QChar::Direction d, BidiState &bidi); 0103 static void appendRun(BidiState &bidi); 0104 0105 static int getBPMWidth(int childValue, Length cssUnit) 0106 { 0107 if (!cssUnit.isAuto()) { 0108 return (cssUnit.isFixed() ? cssUnit.value() : childValue); 0109 } 0110 return 0; 0111 } 0112 0113 static int getBorderPaddingMargin(RenderObject *child, bool endOfInline) 0114 { 0115 RenderStyle *cstyle = child->style(); 0116 int result = 0; 0117 bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline; 0118 result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), 0119 (leftSide ? cstyle->marginLeft() : 0120 cstyle->marginRight())); 0121 result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), 0122 (leftSide ? cstyle->paddingLeft() : 0123 cstyle->paddingRight())); 0124 result += leftSide ? child->borderLeft() : child->borderRight(); 0125 return result; 0126 } 0127 0128 #ifndef NDEBUG 0129 static bool inBidiRunDetach; 0130 #endif 0131 0132 void BidiRun::detach(RenderArena *renderArena) 0133 { 0134 #ifndef NDEBUG 0135 inBidiRunDetach = true; 0136 #endif 0137 delete this; 0138 #ifndef NDEBUG 0139 inBidiRunDetach = false; 0140 #endif 0141 0142 // Recover the size left there for us by operator delete and free the memory. 0143 renderArena->free(*(size_t *)this, this); 0144 } 0145 0146 void *BidiRun::operator new(size_t sz, RenderArena *renderArena) throw() 0147 { 0148 return renderArena->allocate(sz); 0149 } 0150 0151 void BidiRun::operator delete(void *ptr, size_t sz) 0152 { 0153 assert(inBidiRunDetach); 0154 0155 // Stash size where detach can find it. 0156 *(size_t *)ptr = sz; 0157 } 0158 0159 static void deleteBidiRuns(RenderArena *arena) 0160 { 0161 if (!sFirstBidiRun) { 0162 return; 0163 } 0164 0165 BidiRun *curr = sFirstBidiRun; 0166 while (curr) { 0167 BidiRun *s = curr->nextRun; 0168 curr->detach(arena); 0169 curr = s; 0170 } 0171 0172 sFirstBidiRun = nullptr; 0173 sLastBidiRun = nullptr; 0174 sBidiRunCount = 0; 0175 } 0176 0177 // --------------------------------------------------------------------- 0178 0179 /* a small helper class used internally to resolve Bidi embedding levels. 0180 Each line of text caches the embedding level at the start of the line for faster 0181 relayouting 0182 */ 0183 BidiContext::BidiContext(unsigned char l, QChar::Direction e, BidiContext *p, bool o) 0184 : level(l), override(o), dir(e) 0185 { 0186 parent = p; 0187 if (p) { 0188 p->ref(); 0189 basicDir = p->basicDir; 0190 } else { 0191 basicDir = e; 0192 } 0193 count = 0; 0194 } 0195 0196 BidiContext::~BidiContext() 0197 { 0198 if (parent) { 0199 parent->deref(); 0200 } 0201 } 0202 0203 void BidiContext::ref() const 0204 { 0205 count++; 0206 } 0207 0208 void BidiContext::deref() const 0209 { 0210 count--; 0211 if (count <= 0) { 0212 delete this; 0213 } 0214 } 0215 0216 // --------------------------------------------------------------------- 0217 0218 inline bool operator==(const BidiContext &c1, const BidiContext &c2) 0219 { 0220 if (&c1 == &c2) { 0221 return true; 0222 } 0223 if (c1.level != c2.level || c1.override != c2.override || c1.dir != c2.dir || c1.basicDir != c2.basicDir) { 0224 return false; 0225 } 0226 if (!c1.parent) { 0227 return !c2.parent; 0228 } 0229 return c2.parent && *c1.parent == *c2.parent; 0230 } 0231 0232 inline bool operator==(const BidiIterator &it1, const BidiIterator &it2) 0233 { 0234 if (it1.pos != it2.pos) { 0235 return false; 0236 } 0237 if (it1.obj != it2.obj) { 0238 return false; 0239 } 0240 return true; 0241 } 0242 0243 inline bool operator!=(const BidiIterator &it1, const BidiIterator &it2) 0244 { 0245 if (it1.pos != it2.pos) { 0246 return true; 0247 } 0248 if (it1.obj != it2.obj) { 0249 return true; 0250 } 0251 return false; 0252 } 0253 0254 inline bool operator==(const BidiStatus &status1, const BidiStatus &status2) 0255 { 0256 return status1.eor == status2.eor && status1.last == status2.last && status1.lastStrong == status2.lastStrong; 0257 } 0258 0259 inline bool operator!=(const BidiStatus &status1, const BidiStatus &status2) 0260 { 0261 return !(status1 == status2); 0262 } 0263 0264 // when modifying this function, make sure you check InlineMinMaxIterator::next() as well. 0265 static inline RenderObject *Bidinext(RenderObject *par, RenderObject *current, BidiState *bidi = nullptr, 0266 bool skipInlines = true, bool *endOfInline = nullptr) 0267 { 0268 RenderObject *next = nullptr; 0269 bool oldEndOfInline = endOfInline ? *endOfInline : false; 0270 if (oldEndOfInline) { 0271 *endOfInline = false; 0272 } 0273 while (current != nullptr) { 0274 //qCDebug(KHTML_LOG) << "current = " << current; 0275 if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned()) { 0276 next = current->firstChild(); 0277 if (next && bidi) { 0278 EUnicodeBidi ub = next->style()->unicodeBidi(); 0279 if (ub != UBNormal && !emptyRun) { 0280 EDirection dir = next->style()->direction(); 0281 QChar::Direction d = (ub == Embed ? (dir == RTL ? QChar::DirRLE : QChar::DirLRE) 0282 : (dir == RTL ? QChar::DirRLO : QChar::DirLRO)); 0283 embed(d, *bidi); 0284 } 0285 } 0286 } 0287 if (!next) { 0288 if (!skipInlines && !oldEndOfInline && current->isInlineFlow() && endOfInline) { 0289 next = current; 0290 *endOfInline = true; 0291 break; 0292 } 0293 0294 while (current && current != par) { 0295 next = current->nextSibling(); 0296 if (next) { 0297 break; 0298 } 0299 if (bidi && current->style()->unicodeBidi() != UBNormal && !emptyRun) { 0300 embed(QChar::DirPDF, *bidi); 0301 } 0302 current = current->parent(); 0303 if (!skipInlines && current && current != par && current->isInlineFlow() && endOfInline) { 0304 next = current; 0305 *endOfInline = true; 0306 break; 0307 } 0308 } 0309 } 0310 0311 if (!next) { 0312 break; 0313 } 0314 0315 if (next->isText() || next->isBR() || next->isFloating() || next->isReplaced() || next->isPositioned() || next->isGlyph() 0316 || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines. 0317 && next->isInlineFlow())) { 0318 break; 0319 } 0320 current = next; 0321 next = nullptr; 0322 } 0323 return next; 0324 } 0325 0326 static RenderObject *first(RenderObject *par, BidiState *bidi, bool skipInlines = true) 0327 { 0328 if (!par->firstChild()) { 0329 return nullptr; 0330 } 0331 RenderObject *o = par->firstChild(); 0332 0333 if (o->isInlineFlow()) { 0334 if (skipInlines && o->firstChild()) { 0335 o = Bidinext(par, o, bidi, skipInlines); 0336 } else { 0337 return o; // Never skip empty inlines. 0338 } 0339 } 0340 0341 if (o && !o->isText() && !o->isBR() && !o->isReplaced() && !o->isFloating() && !o->isPositioned() && !o->isGlyph()) { 0342 o = Bidinext(par, o, bidi, skipInlines); 0343 } 0344 return o; 0345 } 0346 0347 inline void BidiIterator::increment(BidiState *bidi, bool skipInlines) 0348 { 0349 if (!obj) { 0350 return; 0351 } 0352 if (obj->isText()) { 0353 pos++; 0354 if (pos >= static_cast<RenderText *>(obj)->stringLength()) { 0355 obj = Bidinext(par, obj, bidi, skipInlines); 0356 pos = 0; 0357 } 0358 } else { 0359 obj = Bidinext(par, obj, bidi, skipInlines, &endOfInline); 0360 pos = 0; 0361 } 0362 } 0363 0364 inline bool BidiIterator::atEnd() const 0365 { 0366 if (!obj) { 0367 return true; 0368 } 0369 return false; 0370 } 0371 0372 const QChar &BidiIterator::current() const 0373 { 0374 static QChar nonBreakingSpace(0xA0); 0375 0376 if (!obj || !obj->isText()) { 0377 return nonBreakingSpace; 0378 } 0379 0380 RenderText *text = static_cast<RenderText *>(obj); 0381 if (!text->text()) { 0382 return nonBreakingSpace; 0383 } 0384 0385 return text->text()[pos]; 0386 } 0387 0388 inline QChar::Direction BidiIterator::direction() const 0389 { 0390 if (!obj || !obj->isText()) { 0391 return QChar::DirON; 0392 } 0393 0394 RenderText *renderTxt = static_cast<RenderText *>(obj); 0395 if (pos >= renderTxt->stringLength()) { 0396 return QChar::DirON; 0397 } 0398 0399 return renderTxt->text()[pos].direction(); 0400 } 0401 0402 // ------------------------------------------------------------------------------------------------- 0403 0404 static void addRun(BidiRun *bidiRun) 0405 { 0406 if (!sFirstBidiRun) { 0407 sFirstBidiRun = sLastBidiRun = bidiRun; 0408 } else { 0409 sLastBidiRun->nextRun = bidiRun; 0410 sLastBidiRun = bidiRun; 0411 } 0412 sBidiRunCount++; 0413 bidiRun->compact = sBuildingCompactRuns; 0414 0415 // Compute the number of spaces in this run, 0416 if (bidiRun->obj && bidiRun->obj->isText()) { 0417 RenderText *text = static_cast<RenderText *>(bidiRun->obj); 0418 if (text->text()) { 0419 for (int i = bidiRun->start; i < bidiRun->stop; i++) { 0420 const QChar c = text->text()[i]; 0421 if (c.unicode() == '\n' || c.category() == QChar::Separator_Space) { 0422 numSpaces++; 0423 } 0424 } 0425 } 0426 } 0427 } 0428 0429 static void reverseRuns(int start, int end) 0430 { 0431 if (start >= end) { 0432 return; 0433 } 0434 0435 assert(start >= 0 && end < sBidiRunCount); 0436 0437 // Get the item before the start of the runs to reverse and put it in 0438 // |beforeStart|. |curr| should point to the first run to reverse. 0439 BidiRun *curr = sFirstBidiRun; 0440 BidiRun *beforeStart = nullptr; 0441 int i = 0; 0442 while (i < start) { 0443 i++; 0444 beforeStart = curr; 0445 curr = curr->nextRun; 0446 } 0447 0448 BidiRun *startRun = curr; 0449 while (i < end) { 0450 i++; 0451 curr = curr->nextRun; 0452 } 0453 BidiRun *endRun = curr; 0454 BidiRun *afterEnd = curr->nextRun; 0455 0456 i = start; 0457 curr = startRun; 0458 BidiRun *newNext = afterEnd; 0459 while (i <= end) { 0460 // Do the reversal. 0461 BidiRun *next = curr->nextRun; 0462 curr->nextRun = newNext; 0463 newNext = curr; 0464 curr = next; 0465 i++; 0466 } 0467 0468 // Now hook up beforeStart and afterEnd to the newStart and newEnd. 0469 if (beforeStart) { 0470 beforeStart->nextRun = endRun; 0471 } else { 0472 sFirstBidiRun = endRun; 0473 } 0474 0475 startRun->nextRun = afterEnd; 0476 if (!afterEnd) { 0477 sLastBidiRun = startRun; 0478 } 0479 } 0480 0481 static void chopMidpointsAt(RenderObject *obj, uint pos) 0482 { 0483 if (!sNumMidpoints) { 0484 return; 0485 } 0486 BidiIterator *midpoints = smidpoints->data(); 0487 for (uint i = 0; i < sNumMidpoints; i++) { 0488 const BidiIterator &point = midpoints[i]; 0489 if (point.obj == obj && point.pos == pos) { 0490 sNumMidpoints = i; 0491 break; 0492 } 0493 } 0494 } 0495 0496 static void checkMidpoints(BidiIterator &lBreak) 0497 { 0498 // Check to see if our last midpoint is a start point beyond the line break. If so, 0499 // shave it off the list, and shave off a trailing space if the previous end point isn't 0500 // white-space: pre. 0501 if (lBreak.obj && sNumMidpoints && sNumMidpoints % 2 == 0) { 0502 BidiIterator *midpoints = smidpoints->data(); 0503 BidiIterator &endpoint = midpoints[sNumMidpoints - 2]; 0504 const BidiIterator &startpoint = midpoints[sNumMidpoints - 1]; 0505 BidiIterator currpoint = endpoint; 0506 while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) { 0507 currpoint.increment(); 0508 } 0509 if (currpoint == lBreak) { 0510 // We hit the line break before the start point. Shave off the start point. 0511 sNumMidpoints--; 0512 if (!endpoint.obj->style()->preserveWS()) { 0513 if (endpoint.obj->isText()) { 0514 // Don't shave a character off the endpoint if it was from a soft hyphen. 0515 RenderText *textObj = static_cast<RenderText *>(endpoint.obj); 0516 if (endpoint.pos + 1 < textObj->length() && 0517 textObj->text()[endpoint.pos + 1].unicode() == SOFT_HYPHEN) { 0518 return; 0519 } 0520 } 0521 endpoint.pos--; 0522 } 0523 } 0524 } 0525 } 0526 0527 static void addMidpoint(const BidiIterator &midpoint) 0528 { 0529 if (!smidpoints) { 0530 return; 0531 } 0532 0533 if (smidpoints->size() <= (int)sNumMidpoints) { 0534 smidpoints->resize(sNumMidpoints + 10); 0535 } 0536 0537 BidiIterator *midpoints = smidpoints->data(); 0538 0539 // do not place midpoints in inline flows that are going to be skipped by the bidi iteration process. 0540 // Place them at the next non-skippable object instead. 0541 // #### eventually, we may want to have the same iteration in bidi and in findNextLineBreak, 0542 // then this extra complexity can go away. 0543 if (midpoint.obj && midpoint.obj->isInlineFlow() && (midpoint.obj->firstChild() || midpoint.endOfInline)) { 0544 BidiIterator n = midpoint; 0545 n.increment(); 0546 assert(!n.endOfInline); 0547 // we'll recycle the endOfInline flag to mean : don't include this stop point, stop right before it. 0548 // this is necessary because we just advanced our position to skip an inline, so we passed the real stop point 0549 n.endOfInline = true; 0550 if (!n.atEnd()) { 0551 midpoints[sNumMidpoints++] = n; 0552 } 0553 } else { 0554 assert(!midpoint.endOfInline); 0555 midpoints[sNumMidpoints++] = midpoint; 0556 } 0557 } 0558 0559 static void appendRunsForObject(int start, int end, RenderObject *obj, BidiState &bidi) 0560 { 0561 if (start > end || obj->isFloating() || 0562 (obj->isPositioned() && !obj->hasStaticX() && !obj->hasStaticY())) { 0563 return; 0564 } 0565 0566 bool haveNextMidpoint = (smidpoints && sCurrMidpoint < sNumMidpoints); 0567 BidiIterator nextMidpoint; 0568 if (haveNextMidpoint) { 0569 nextMidpoint = smidpoints->at(sCurrMidpoint); 0570 } 0571 if (betweenMidpoints) { 0572 if (!(haveNextMidpoint && nextMidpoint.obj == obj)) { 0573 return; 0574 } 0575 // This is a new start point. Stop ignoring objects and 0576 // adjust our start. 0577 betweenMidpoints = false; 0578 start = nextMidpoint.pos; 0579 sCurrMidpoint++; 0580 if (start < end) { 0581 return appendRunsForObject(start, end, obj, bidi); 0582 } 0583 } else { 0584 if (!smidpoints || !haveNextMidpoint || (obj != nextMidpoint.obj)) { 0585 addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); 0586 return; 0587 } 0588 0589 // An end midpoint has been encountered within our object. We 0590 // need to go ahead and append a run with our endpoint. 0591 if (int(nextMidpoint.pos + 1) <= end) { 0592 betweenMidpoints = true; 0593 sCurrMidpoint++; 0594 if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. 0595 if (!nextMidpoint.endOfInline) // In this context, this flag means the stop point is exclusive, not inclusive (see addMidpoint). 0596 addRun(new(obj->renderArena()) 0597 BidiRun(start, nextMidpoint.pos + 1, obj, bidi.context, dir)); 0598 return appendRunsForObject(nextMidpoint.pos + 1, end, obj, bidi); 0599 } 0600 } else { 0601 addRun(new(obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); 0602 } 0603 } 0604 } 0605 0606 static void appendRun(BidiState &bidi) 0607 { 0608 if (emptyRun) { 0609 return; 0610 } 0611 #if BIDI_DEBUG > 1 0612 qCDebug(KHTML_LOG) << "appendRun: dir=" << (int)dir; 0613 #endif 0614 0615 int start = bidi.sor.pos; 0616 RenderObject *obj = bidi.sor.obj; 0617 while (obj && obj != bidi.eor.obj) { 0618 appendRunsForObject(start, obj->length(), obj, bidi); 0619 start = 0; 0620 obj = Bidinext(bidi.sor.par, obj); 0621 } 0622 if (obj) { 0623 appendRunsForObject(start, bidi.eor.pos + 1, obj, bidi); 0624 } 0625 0626 bidi.eor.increment(); 0627 bidi.sor = bidi.eor; 0628 dir = QChar::DirON; 0629 bidi.status.eor = QChar::DirON; 0630 } 0631 0632 static void embed(QChar::Direction d, BidiState &bidi) 0633 { 0634 #if BIDI_DEBUG > 1 0635 qDebug("*** embed dir=%d emptyrun=%d", d, emptyRun); 0636 #endif 0637 if (d == QChar::DirPDF) { 0638 BidiContext *c = bidi.context->parent; 0639 if (c) { 0640 if (bidi.eor != bidi.last) { 0641 appendRun(bidi); 0642 bidi.eor = bidi.last; 0643 } 0644 appendRun(bidi); 0645 emptyRun = true; 0646 bidi.status.last = bidi.context->dir; 0647 bidi.context->deref(); 0648 bidi.context = c; 0649 if (bidi.context->override) { 0650 dir = bidi.context->dir; 0651 } else { 0652 dir = QChar::DirON; 0653 } 0654 bidi.status.lastStrong = bidi.context->dir; 0655 } 0656 } else { 0657 QChar::Direction runDir; 0658 if (d == QChar::DirRLE || d == QChar::DirRLO) { 0659 runDir = QChar::DirR; 0660 } else { 0661 runDir = QChar::DirL; 0662 } 0663 bool override; 0664 if (d == QChar::DirLRO || d == QChar::DirRLO) { 0665 override = true; 0666 } else { 0667 override = false; 0668 } 0669 0670 unsigned char level = bidi.context->level; 0671 if (runDir == QChar::DirR) { 0672 if (level % 2) { // we have an odd level 0673 level += 2; 0674 } else { 0675 level++; 0676 } 0677 } else { 0678 if (level % 2) { // we have an odd level 0679 level++; 0680 } else { 0681 level += 2; 0682 } 0683 } 0684 0685 if (level < 61) { 0686 if (bidi.eor != bidi.last) { 0687 appendRun(bidi); 0688 bidi.eor = bidi.last; 0689 } 0690 appendRun(bidi); 0691 emptyRun = true; 0692 0693 bidi.context = new BidiContext(level, runDir, bidi.context, override); 0694 bidi.context->ref(); 0695 dir = runDir; 0696 bidi.status.last = runDir; 0697 bidi.status.lastStrong = runDir; 0698 bidi.status.eor = runDir; 0699 } 0700 } 0701 } 0702 0703 InlineFlowBox *RenderBlock::createLineBoxes(RenderObject *obj) 0704 { 0705 // See if we have an unconstructed line box for this object that is also 0706 // the last item on the line. 0707 KHTMLAssert(obj->isInlineFlow() || obj == this); 0708 RenderFlow *flow = static_cast<RenderFlow *>(obj); 0709 0710 // Get the last box we made for this render object. 0711 InlineFlowBox *box = flow->lastLineBox(); 0712 0713 // If this box is constructed then it is from a previous line, and we need 0714 // to make a new box for our line. If this box is unconstructed but it has 0715 // something following it on the line, then we know we have to make a new box 0716 // as well. In this situation our inline has actually been split in two on 0717 // the same line (this can happen with very fancy language mixtures). 0718 if (!box || box->isConstructed() || box->nextOnLine()) { 0719 // We need to make a new box for this render object. Once 0720 // made, we need to place it at the end of the current line. 0721 InlineBox *newBox = obj->createInlineBox(false, obj == this); 0722 KHTMLAssert(newBox->isInlineFlowBox()); 0723 box = static_cast<InlineFlowBox *>(newBox); 0724 box->setFirstLineStyleBit(m_firstLine); 0725 0726 // We have a new box. Append it to the inline box we get by constructing our 0727 // parent. If we have hit the block itself, then |box| represents the root 0728 // inline box for the line, and it doesn't have to be appended to any parent 0729 // inline. 0730 if (obj != this) { 0731 InlineFlowBox *parentBox = createLineBoxes(obj->parent()); 0732 parentBox->addToLine(box); 0733 } 0734 } 0735 0736 return box; 0737 } 0738 0739 RootInlineBox *RenderBlock::constructLine(const BidiIterator &/*start*/, const BidiIterator &end) 0740 { 0741 if (!sFirstBidiRun) { 0742 return nullptr; // We had no runs. Don't make a root inline box at all. The line is empty. 0743 } 0744 0745 InlineFlowBox *parentBox = nullptr; 0746 for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) { 0747 // Create a box for our object. 0748 r->box = r->obj->createInlineBox(r->obj->isPositioned(), false); 0749 0750 // If we have no parent box yet, or if the run is not simply a sibling, 0751 // then we need to construct inline boxes as necessary to properly enclose the 0752 // run's inline box. 0753 if (!parentBox || (parentBox->object() != r->obj->parent())) 0754 // Create new inline boxes all the way back to the appropriate insertion point. 0755 { 0756 parentBox = createLineBoxes(r->obj->parent()); 0757 } 0758 0759 // Append the inline box to this line. 0760 parentBox->addToLine(r->box); 0761 } 0762 0763 // We should have a root inline box. It should be unconstructed and 0764 // be the last continuation of our line list. 0765 KHTMLAssert(lastLineBox() && !lastLineBox()->isConstructed()); 0766 0767 // Set bits on our inline flow boxes that indicate which sides should 0768 // paint borders/margins/padding. This knowledge will ultimately be used when 0769 // we determine the horizontal positions and widths of all the inline boxes on 0770 // the line. 0771 RenderObject *endObject = nullptr; 0772 bool lastLine = !end.obj; 0773 if (end.obj && end.pos == 0) { 0774 endObject = end.obj; 0775 } 0776 lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject); 0777 0778 // Now mark the line boxes as being constructed. 0779 lastLineBox()->setConstructed(); 0780 0781 // Return the last line. 0782 return lastRootBox(); 0783 } 0784 0785 void RenderBlock::computeHorizontalPositionsForLine(InlineFlowBox *lineBox, BidiState &bidi) 0786 { 0787 // First determine our total width. 0788 int totWidth = lineBox->getFlowSpacingWidth(); 0789 BidiRun *r = nullptr; 0790 for (r = sFirstBidiRun; r; r = r->nextRun) { 0791 if (r->obj->isPositioned()) { 0792 continue; // Positioned objects are only participating to figure out their 0793 } 0794 // correct static x position. They have no effect on the width. 0795 if (r->obj->isText()) { 0796 r->box->setWidth(static_cast<RenderText *>(r->obj)->width(r->start, r->stop - r->start, m_firstLine)); 0797 } else if (!r->obj->isInlineFlow()) { 0798 r->obj->calcWidth(); // Is this really needed or the object width is already correct here ? 0799 r->box->setWidth(r->obj->width()); 0800 totWidth += r->obj->marginLeft() + r->obj->marginRight(); 0801 } 0802 totWidth += r->box->width(); 0803 } 0804 0805 // Armed with the total width of the line (without justification), 0806 // we now examine our text-align property in order to determine where to position the 0807 // objects horizontally. The total width of the line can be increased if we end up 0808 // justifying text. 0809 int x = leftOffset(m_height); 0810 int availableWidth = lineWidth(m_height); 0811 switch (style()->textAlign()) { 0812 case LEFT: 0813 case KHTML_LEFT: 0814 if (style()->direction() == RTL && totWidth > availableWidth) { 0815 x -= (totWidth - availableWidth); 0816 } 0817 numSpaces = 0; 0818 break; 0819 case JUSTIFY: 0820 if (numSpaces != 0 && !bidi.current.atEnd() && !bidi.current.obj->isBR()) { 0821 break; 0822 } 0823 // fall through 0824 case TAAUTO: 0825 numSpaces = 0; 0826 // for right to left fall through to right aligned 0827 if (bidi.context->basicDir == QChar::DirL) { 0828 break; 0829 } 0830 case RIGHT: 0831 case KHTML_RIGHT: 0832 if (style()->direction() == RTL || totWidth < availableWidth) { 0833 x += availableWidth - totWidth; 0834 } 0835 numSpaces = 0; 0836 break; 0837 case CENTER: 0838 case KHTML_CENTER: 0839 int xd = (availableWidth - totWidth) / 2; 0840 x += xd > 0 ? xd : 0; 0841 numSpaces = 0; 0842 break; 0843 } 0844 0845 if (numSpaces > 0) { 0846 for (r = sFirstBidiRun; r; r = r->nextRun) { 0847 int spaceAdd = 0; 0848 if (numSpaces > 0 && r->obj->isText()) { 0849 // get the number of spaces in the run 0850 int spaces = 0; 0851 for (int i = r->start; i < r->stop; i++) { 0852 const QChar c = static_cast<RenderText *>(r->obj)->text()[i]; 0853 if (c.category() == QChar::Separator_Space || c == '\n') { 0854 spaces++; 0855 } 0856 } 0857 0858 KHTMLAssert(spaces <= numSpaces); 0859 0860 // Only justify text with white-space: normal. 0861 if (r->obj->style()->whiteSpace() == NORMAL) { 0862 spaceAdd = (availableWidth - totWidth) * spaces / numSpaces; 0863 spaceAdd = qMax(0, spaceAdd); 0864 static_cast<InlineTextBox *>(r->box)->setSpaceAdd(spaceAdd); 0865 totWidth += spaceAdd; 0866 } 0867 numSpaces -= spaces; 0868 } 0869 } 0870 } 0871 0872 // The widths of all runs are now known. We can now place every inline box (and 0873 // compute accurate widths for the inline flow boxes). 0874 int rightPos = lineBox->placeBoxesHorizontally(x); 0875 if (rightPos > m_overflowWidth) { 0876 m_overflowWidth = rightPos; // FIXME: Work for rtl overflow also. 0877 } 0878 if (x < 0) { 0879 m_overflowLeft = qMin(m_overflowLeft, x); 0880 } 0881 } 0882 0883 void RenderBlock::computeVerticalPositionsForLine(RootInlineBox *lineBox) 0884 { 0885 lineBox->verticallyAlignBoxes(m_height); 0886 lineBox->setBlockHeight(m_height); 0887 0888 // Check for page-breaks 0889 if (canvas()->pagedMode() && !lineBox->afterPageBreak()) 0890 // If we get a page-break we might need to redo the line-break 0891 if (clearLineOfPageBreaks(lineBox) && hasFloats()) { 0892 return; 0893 } 0894 0895 // See if the line spilled out. If so set overflow height accordingly. 0896 int bottomOfLine = lineBox->bottomOverflow(); 0897 if (bottomOfLine > m_height && bottomOfLine > m_overflowHeight) { 0898 m_overflowHeight = bottomOfLine; 0899 } 0900 0901 bool beforeContent = true; 0902 0903 // Now make sure we place replaced render objects correctly. 0904 for (BidiRun *r = sFirstBidiRun; r; r = r->nextRun) { 0905 0906 // For positioned placeholders, cache the static Y position an object with non-inline display would have. 0907 // Either it is unchanged if it comes before any real linebox, or it must clear the current line (already accounted in m_height). 0908 // This value will be picked up by position() if relevant. 0909 if (r->obj->isPositioned()) { 0910 r->box->setYPos(beforeContent && r->obj->isBox() ? static_cast<RenderBox *>(r->obj)->staticY() : m_height); 0911 } else if (beforeContent) { 0912 beforeContent = false; 0913 } 0914 0915 // Position is used to properly position both replaced elements and 0916 // to update the static normal flow x/y of positioned elements. 0917 r->obj->position(r->box, r->start, r->stop - r->start, r->level % 2); 0918 } 0919 } 0920 0921 bool RenderBlock::clearLineOfPageBreaks(InlineFlowBox *lineBox) 0922 { 0923 bool doPageBreak = false; 0924 // Check for physical page-breaks 0925 int xpage = crossesPageBreak(lineBox->topOverflow(), lineBox->bottomOverflow()); 0926 if (xpage) { 0927 #ifdef PAGE_DEBUG 0928 qCDebug(KHTML_LOG) << renderName() << " Line crosses to page " << xpage; 0929 qCDebug(KHTML_LOG) << renderName() << " at pos " << lineBox->yPos() << " height " << lineBox->height(); 0930 #endif 0931 0932 doPageBreak = true; 0933 // check page-break-inside 0934 if (!style()->pageBreakInside()) { 0935 if (parent()->canClear(this, PageBreakNormal)) { 0936 setNeedsPageClear(true); 0937 doPageBreak = false; 0938 } 0939 #ifdef PAGE_DEBUG 0940 else { 0941 qCDebug(KHTML_LOG) << "Ignoring page-break-inside: avoid"; 0942 } 0943 #endif 0944 } 0945 // check orphans 0946 int orphans = 0; 0947 InlineRunBox *box = lineBox->prevLineBox(); 0948 while (box && orphans < style()->orphans()) { 0949 orphans++; 0950 box = box->prevLineBox(); 0951 } 0952 0953 if (orphans == 0) { 0954 setNeedsPageClear(true); 0955 doPageBreak = false; 0956 } else if (orphans < style()->orphans()) { 0957 #ifdef PAGE_DEBUG 0958 qCDebug(KHTML_LOG) << "Orphans: " << orphans; 0959 #endif 0960 // Orphans is a level 2 page-break rule and can be broken only 0961 // if the break is physically required. 0962 if (parent()->canClear(this, PageBreakHarder)) { 0963 // move block instead 0964 setNeedsPageClear(true); 0965 doPageBreak = false; 0966 } 0967 #ifdef PAGE_DEBUG 0968 else { 0969 qCDebug(KHTML_LOG) << "Ignoring violated orphans"; 0970 } 0971 #endif 0972 } 0973 if (doPageBreak) { 0974 #ifdef PAGE_DEBUG 0975 int oldYPos = lineBox->yPos(); 0976 #endif 0977 int pTop = pageTopAfter(lineBox->yPos()); 0978 m_height = pTop; 0979 lineBox->setAfterPageBreak(true); 0980 lineBox->verticallyAlignBoxes(m_height); 0981 if (lineBox->yPos() < pTop) { 0982 // ### serious crap. render_line is sometimes placing lines too high 0983 // qCDebug(KHTML_LOG) << "page top overflow by repositioned line"; 0984 int heightIncrease = pTop - lineBox->yPos(); 0985 m_height = pTop + heightIncrease; 0986 lineBox->verticallyAlignBoxes(m_height); 0987 } 0988 #ifdef PAGE_DEBUG 0989 qCDebug(KHTML_LOG) << "Cleared line " << lineBox->yPos() - oldYPos << "px"; 0990 #endif 0991 setContainsPageBreak(true); 0992 } 0993 } 0994 return doPageBreak; 0995 } 0996 0997 // collects one line of the paragraph and transforms it to visual order 0998 void RenderBlock::bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi) 0999 { 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 }