fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / khtml / rendering / bidi.cpp
blobc682d9402bc3dc21f8f56ff1dbf710fbee03a08f
1 /**
2 * This file is part of the html renderer for KDE.
4 * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org)
5 * (C) 2003-2007 Apple Computer, Inc.
6 * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
7 * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org)
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.
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.
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.
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>
35 #include "kdebug.h"
37 #include <limits.h>
39 // SVG
40 #include "rendering/SVGRootInlineBox.h"
41 #include "rendering/SVGInlineTextBox.h"
43 #define BIDI_DEBUG 0
44 //#define DEBUG_LINEBREAKS
45 //#define PAGE_DEBUG
47 namespace khtml {
50 // an iterator which goes through a BidiParagraph
51 struct BidiIterator
53 BidiIterator() : par(0), obj(0), pos(0), endOfInline(false) {}
54 BidiIterator(RenderBlock *_par, RenderObject *_obj, unsigned int _pos, bool eoi=false) : par(_par), obj(_obj), pos(_pos), endOfInline(eoi) {}
56 void increment( BidiState *bidi=0, bool skipInlines=true );
58 bool atEnd() const;
60 const QChar &current() const;
61 QChar::Direction direction() const;
63 RenderBlock *par;
64 RenderObject *obj;
65 unsigned int pos;
66 bool endOfInline;
69 struct BidiState {
70 BidiState() : context(0) {}
72 BidiIterator sor;
73 BidiIterator eor;
74 BidiIterator last;
75 BidiIterator current;
76 BidiContext *context;
77 BidiStatus status;
80 // Used to track a list of chained bidi runs.
81 static BidiRun* sFirstBidiRun;
82 static BidiRun* sLastBidiRun;
83 static int sBidiRunCount;
84 static BidiRun* sCompactFirstBidiRun;
85 static BidiRun* sCompactLastBidiRun;
86 static int sCompactBidiRunCount;
87 static bool sBuildingCompactRuns;
89 // Midpoint globals. The goal is not to do any allocation when dealing with
90 // these midpoints, so we just keep an array around and never clear it. We track
91 // the number of items and position using the two other variables.
92 static QVector<BidiIterator> *smidpoints;
93 static uint sNumMidpoints;
94 static uint sCurrMidpoint;
95 static bool betweenMidpoints;
97 static bool isLineEmpty = true;
98 static bool previousLineBrokeAtBR = false;
99 static QChar::Direction dir = QChar::DirON;
100 static bool emptyRun = true;
101 static int numSpaces;
103 static void embed( QChar::Direction d, BidiState &bidi );
104 static void appendRun( BidiState &bidi );
106 static int getBPMWidth(int childValue, Length cssUnit)
108 if (!cssUnit.isVariable())
109 return (cssUnit.isFixed() ? cssUnit.value() : childValue);
110 return 0;
113 static int getBorderPaddingMargin(RenderObject* child, bool endOfInline)
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;
128 #ifndef NDEBUG
129 static bool inBidiRunDetach;
130 #endif
132 void BidiRun::detach(RenderArena* renderArena)
134 #ifndef NDEBUG
135 inBidiRunDetach = true;
136 #endif
137 delete this;
138 #ifndef NDEBUG
139 inBidiRunDetach = false;
140 #endif
142 // Recover the size left there for us by operator delete and free the memory.
143 renderArena->free(*(size_t *)this, this);
146 void* BidiRun::operator new(size_t sz, RenderArena* renderArena) throw()
148 return renderArena->allocate(sz);
151 void BidiRun::operator delete(void* ptr, size_t sz)
153 assert(inBidiRunDetach);
155 // Stash size where detach can find it.
156 *(size_t*)ptr = sz;
159 static void deleteBidiRuns(RenderArena* arena)
161 if (!sFirstBidiRun)
162 return;
164 BidiRun* curr = sFirstBidiRun;
165 while (curr) {
166 BidiRun* s = curr->nextRun;
167 curr->detach(arena);
168 curr = s;
171 sFirstBidiRun = 0;
172 sLastBidiRun = 0;
173 sBidiRunCount = 0;
176 // ---------------------------------------------------------------------
178 /* a small helper class used internally to resolve Bidi embedding levels.
179 Each line of text caches the embedding level at the start of the line for faster
180 relayouting
182 BidiContext::BidiContext(unsigned char l, QChar::Direction e, BidiContext *p, bool o)
183 : level(l) , override(o), dir(e)
185 parent = p;
186 if(p) {
187 p->ref();
188 basicDir = p->basicDir;
189 } else
190 basicDir = e;
191 count = 0;
194 BidiContext::~BidiContext()
196 if(parent) parent->deref();
199 void BidiContext::ref() const
201 count++;
204 void BidiContext::deref() const
206 count--;
207 if(count <= 0) delete this;
210 // ---------------------------------------------------------------------
213 inline bool operator==(const BidiContext& c1, const BidiContext& c2)
215 if (&c1 == &c2)
216 return true;
217 if (c1.level != c2.level || c1.override != c2.override || c1.dir != c2.dir || c1.basicDir != c2.basicDir)
218 return false;
219 if (!c1.parent)
220 return !c2.parent;
221 return c2.parent && *c1.parent == *c2.parent;
224 inline bool operator==( const BidiIterator &it1, const BidiIterator &it2 )
226 if(it1.pos != it2.pos) return false;
227 if(it1.obj != it2.obj) return false;
228 return true;
231 inline bool operator!=( const BidiIterator &it1, const BidiIterator &it2 )
233 if(it1.pos != it2.pos) return true;
234 if(it1.obj != it2.obj) return true;
235 return false;
238 inline bool operator==(const BidiStatus& status1, const BidiStatus& status2)
240 return status1.eor == status2.eor && status1.last == status2.last && status1.lastStrong == status2.lastStrong;
243 inline bool operator!=(const BidiStatus& status1, const BidiStatus& status2)
245 return !(status1 == status2);
248 // when modifying this function, make sure you check InlineMinMaxIterator::next() as well.
249 static inline RenderObject *Bidinext(RenderObject *par, RenderObject *current, BidiState *bidi=0,
250 bool skipInlines = true, bool* endOfInline = 0)
252 RenderObject *next = 0;
253 bool oldEndOfInline = endOfInline ? *endOfInline : false;
254 if (oldEndOfInline)
255 *endOfInline = false;
256 while(current != 0)
258 //kDebug( 6040 ) << "current = " << current;
259 if (!oldEndOfInline && !current->isFloating() && !current->isReplaced() && !current->isPositioned()) {
260 next = current->firstChild();
261 if ( next && bidi) {
262 EUnicodeBidi ub = next->style()->unicodeBidi();
263 if ( ub != UBNormal && !emptyRun ) {
264 EDirection dir = next->style()->direction();
265 QChar::Direction d = ( ub == Embed ? ( dir == RTL ? QChar::DirRLE : QChar::DirLRE )
266 : ( dir == RTL ? QChar::DirRLO : QChar::DirLRO ) );
267 embed( d, *bidi );
271 if (!next) {
272 if (!skipInlines && !oldEndOfInline && current->isInlineFlow() && endOfInline) {
273 next = current;
274 *endOfInline = true;
275 break;
278 while (current && current != par) {
279 next = current->nextSibling();
280 if (next) break;
281 if ( bidi && current->style()->unicodeBidi() != UBNormal && !emptyRun ) {
282 embed( QChar::DirPDF, *bidi );
284 current = current->parent();
285 if (!skipInlines && current && current != par && current->isInlineFlow() && endOfInline) {
286 next = current;
287 *endOfInline = true;
288 break;
293 if (!next) break;
295 if (next->isText() || next->isBR() || next->isFloating() || next->isReplaced() || next->isPositioned() || next->isGlyph()
296 || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines.
297 && next->isInlineFlow()))
298 break;
299 current = next;
301 return next;
304 static RenderObject *first( RenderObject *par, BidiState *bidi, bool skipInlines = true )
306 if(!par->firstChild()) return 0;
307 RenderObject *o = par->firstChild();
309 if (o->isInlineFlow()) {
310 if (skipInlines && o->firstChild())
311 o = Bidinext( par, o, bidi, skipInlines );
312 else
313 return o; // Never skip empty inlines.
316 if (o && !o->isText() && !o->isBR() && !o->isReplaced() && !o->isFloating() && !o->isPositioned() && !o->isGlyph())
317 o = Bidinext( par, o, bidi, skipInlines );
318 return o;
321 inline void BidiIterator::increment(BidiState *bidi, bool skipInlines)
323 if(!obj) return;
324 if(obj->isText()) {
325 pos++;
326 if(pos >= static_cast<RenderText *>(obj)->stringLength()) {
327 obj = Bidinext( par, obj, bidi, skipInlines );
328 pos = 0;
330 } else {
331 obj = Bidinext( par, obj, bidi, skipInlines, &endOfInline );
332 pos = 0;
336 inline bool BidiIterator::atEnd() const
338 if(!obj) return true;
339 return false;
342 const QChar &BidiIterator::current() const
344 static QChar nonBreakingSpace(0xA0);
346 if (!obj || !obj->isText())
347 return nonBreakingSpace;
349 RenderText* text = static_cast<RenderText*>(obj);
350 if (!text->text())
351 return nonBreakingSpace;
353 return text->text()[pos];
356 inline QChar::Direction BidiIterator::direction() const
358 if(!obj || !obj->isText() ) return QChar::DirON;
360 RenderText *renderTxt = static_cast<RenderText *>( obj );
361 if ( pos >= renderTxt->stringLength() )
362 return QChar::DirON;
364 return renderTxt->text()[pos].direction();
367 // -------------------------------------------------------------------------------------------------
369 static void addRun(BidiRun* bidiRun)
371 if (!sFirstBidiRun)
372 sFirstBidiRun = sLastBidiRun = bidiRun;
373 else {
374 sLastBidiRun->nextRun = bidiRun;
375 sLastBidiRun = bidiRun;
377 sBidiRunCount++;
378 bidiRun->compact = sBuildingCompactRuns;
380 // Compute the number of spaces in this run,
381 if (bidiRun->obj && bidiRun->obj->isText()) {
382 RenderText* text = static_cast<RenderText*>(bidiRun->obj);
383 if (text->text()) {
384 for (int i = bidiRun->start; i < bidiRun->stop; i++) {
385 const QChar c = text->text()[i];
386 if (c.unicode() == '\n' || c.category() == QChar::Separator_Space)
387 numSpaces++;
393 static void reverseRuns(int start, int end)
395 if (start >= end)
396 return;
398 assert(start >= 0 && end < sBidiRunCount);
400 // Get the item before the start of the runs to reverse and put it in
401 // |beforeStart|. |curr| should point to the first run to reverse.
402 BidiRun* curr = sFirstBidiRun;
403 BidiRun* beforeStart = 0;
404 int i = 0;
405 while (i < start) {
406 i++;
407 beforeStart = curr;
408 curr = curr->nextRun;
411 BidiRun* startRun = curr;
412 while (i < end) {
413 i++;
414 curr = curr->nextRun;
416 BidiRun* endRun = curr;
417 BidiRun* afterEnd = curr->nextRun;
419 i = start;
420 curr = startRun;
421 BidiRun* newNext = afterEnd;
422 while (i <= end) {
423 // Do the reversal.
424 BidiRun* next = curr->nextRun;
425 curr->nextRun = newNext;
426 newNext = curr;
427 curr = next;
428 i++;
431 // Now hook up beforeStart and afterEnd to the newStart and newEnd.
432 if (beforeStart)
433 beforeStart->nextRun = endRun;
434 else
435 sFirstBidiRun = endRun;
437 startRun->nextRun = afterEnd;
438 if (!afterEnd)
439 sLastBidiRun = startRun;
442 static void chopMidpointsAt(RenderObject* obj, uint pos)
444 if (!sNumMidpoints) return;
445 BidiIterator* midpoints = smidpoints->data();
446 for (uint i = 0; i < sNumMidpoints; i++) {
447 const BidiIterator& point = midpoints[i];
448 if (point.obj == obj && point.pos == pos) {
449 sNumMidpoints = i;
450 break;
455 static void checkMidpoints(BidiIterator& lBreak)
457 // Check to see if our last midpoint is a start point beyond the line break. If so,
458 // shave it off the list, and shave off a trailing space if the previous end point isn't
459 // white-space: pre.
460 if (lBreak.obj && sNumMidpoints && sNumMidpoints%2 == 0) {
461 BidiIterator* midpoints = smidpoints->data();
462 BidiIterator& endpoint = midpoints[sNumMidpoints-2];
463 const BidiIterator& startpoint = midpoints[sNumMidpoints-1];
464 BidiIterator currpoint = endpoint;
465 while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak)
466 currpoint.increment();
467 if (currpoint == lBreak) {
468 // We hit the line break before the start point. Shave off the start point.
469 sNumMidpoints--;
470 if (!endpoint.obj->style()->preserveWS()) {
471 if (endpoint.obj->isText()) {
472 // Don't shave a character off the endpoint if it was from a soft hyphen.
473 RenderText* textObj = static_cast<RenderText*>(endpoint.obj);
474 if (endpoint.pos+1 < textObj->length() &&
475 textObj->text()[endpoint.pos+1].unicode() == SOFT_HYPHEN)
476 return;
478 endpoint.pos--;
484 static void addMidpoint(const BidiIterator& midpoint)
486 if (!smidpoints)
487 return;
489 if (smidpoints->size() <= (int)sNumMidpoints)
490 smidpoints->resize(sNumMidpoints+10);
492 BidiIterator* midpoints = smidpoints->data();
494 // do not place midpoints in inline flows that are going to be skipped by the bidi iteration process.
495 // Place them at the next non-skippable object instead.
496 // #### eventually, we may want to have the same iteration in bidi and in findNextLineBreak,
497 // then this extra complexity can go away.
498 if (midpoint.obj && midpoint.obj->isInlineFlow() && (midpoint.obj->firstChild() || midpoint.endOfInline)) {
499 BidiIterator n = midpoint;
500 n.increment();
501 assert(!n.endOfInline);
502 // we'll recycle the endOfInline flag to mean : don't include this stop point, stop right before it.
503 // this is necessary because we just advanced our position to skip an inline, so we passed the real stop point
504 n.endOfInline = true;
505 if (!n.atEnd())
506 midpoints[sNumMidpoints++] = n;
507 } else {
508 assert(!midpoint.endOfInline);
509 midpoints[sNumMidpoints++] = midpoint;
513 static void appendRunsForObject(int start, int end, RenderObject* obj, BidiState &bidi)
515 if (start > end || obj->isFloating() ||
516 (obj->isPositioned() && !obj->hasStaticX() && !obj->hasStaticY()))
517 return;
519 bool haveNextMidpoint = (smidpoints && sCurrMidpoint < sNumMidpoints);
520 BidiIterator nextMidpoint;
521 if (haveNextMidpoint)
522 nextMidpoint = smidpoints->at(sCurrMidpoint);
523 if (betweenMidpoints) {
524 if (!(haveNextMidpoint && nextMidpoint.obj == obj))
525 return;
526 // This is a new start point. Stop ignoring objects and
527 // adjust our start.
528 betweenMidpoints = false;
529 start = nextMidpoint.pos;
530 sCurrMidpoint++;
531 if (start < end)
532 return appendRunsForObject(start, end, obj, bidi);
534 else {
535 if (!smidpoints || !haveNextMidpoint || (obj != nextMidpoint.obj)) {
536 addRun(new (obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir));
537 return;
540 // An end midpoint has been encountered within our object. We
541 // need to go ahead and append a run with our endpoint.
542 if (int(nextMidpoint.pos+1) <= end) {
543 betweenMidpoints = true;
544 sCurrMidpoint++;
545 if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it.
546 if (!nextMidpoint.endOfInline) // In this context, this flag means the stop point is exclusive, not inclusive (see addMidpoint).
547 addRun(new (obj->renderArena())
548 BidiRun(start, nextMidpoint.pos+1, obj, bidi.context, dir));
549 return appendRunsForObject(nextMidpoint.pos+1, end, obj, bidi);
552 else
553 addRun(new (obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir));
557 static void appendRun( BidiState &bidi )
559 if ( emptyRun ) return;
560 #if BIDI_DEBUG > 1
561 kDebug(6041) << "appendRun: dir="<<(int)dir;
562 #endif
564 int start = bidi.sor.pos;
565 RenderObject *obj = bidi.sor.obj;
566 while( obj && obj != bidi.eor.obj ) {
567 appendRunsForObject(start, obj->length(), obj, bidi);
568 start = 0;
569 obj = Bidinext( bidi.sor.par, obj);
571 if (obj)
572 appendRunsForObject(start, bidi.eor.pos+1, obj, bidi);
574 bidi.eor.increment();
575 bidi.sor = bidi.eor;
576 dir = QChar::DirON;
577 bidi.status.eor = QChar::DirON;
580 static void embed( QChar::Direction d, BidiState &bidi )
582 #if BIDI_DEBUG > 1
583 qDebug("*** embed dir=%d emptyrun=%d", d, emptyRun );
584 #endif
585 if ( d == QChar::DirPDF ) {
586 BidiContext *c = bidi.context->parent;
587 if (c) {
588 if ( bidi.eor != bidi.last ) {
589 appendRun( bidi );
590 bidi.eor = bidi.last;
592 appendRun( bidi );
593 emptyRun = true;
594 bidi.status.last = bidi.context->dir;
595 bidi.context->deref();
596 bidi.context = c;
597 if(bidi.context->override)
598 dir = bidi.context->dir;
599 else
600 dir = QChar::DirON;
601 bidi.status.lastStrong = bidi.context->dir;
603 } else {
604 QChar::Direction runDir;
605 if( d == QChar::DirRLE || d == QChar::DirRLO )
606 runDir = QChar::DirR;
607 else
608 runDir = QChar::DirL;
609 bool override;
610 if( d == QChar::DirLRO || d == QChar::DirRLO )
611 override = true;
612 else
613 override = false;
615 unsigned char level = bidi.context->level;
616 if ( runDir == QChar::DirR ) {
617 if(level%2) // we have an odd level
618 level += 2;
619 else
620 level++;
621 } else {
622 if(level%2) // we have an odd level
623 level++;
624 else
625 level += 2;
628 if(level < 61) {
629 if ( bidi.eor != bidi.last ) {
630 appendRun( bidi );
631 bidi.eor = bidi.last;
633 appendRun( bidi );
634 emptyRun = true;
636 bidi.context = new BidiContext(level, runDir, bidi.context, override);
637 bidi.context->ref();
638 dir = runDir;
639 bidi.status.last = runDir;
640 bidi.status.lastStrong = runDir;
641 bidi.status.eor = runDir;
646 InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj)
648 // See if we have an unconstructed line box for this object that is also
649 // the last item on the line.
650 KHTMLAssert(obj->isInlineFlow() || obj == this);
651 RenderFlow* flow = static_cast<RenderFlow*>(obj);
653 // Get the last box we made for this render object.
654 InlineFlowBox* box = flow->lastLineBox();
656 // If this box is constructed then it is from a previous line, and we need
657 // to make a new box for our line. If this box is unconstructed but it has
658 // something following it on the line, then we know we have to make a new box
659 // as well. In this situation our inline has actually been split in two on
660 // the same line (this can happen with very fancy language mixtures).
661 if (!box || box->isConstructed() || box->nextOnLine()) {
662 // We need to make a new box for this render object. Once
663 // made, we need to place it at the end of the current line.
664 InlineBox* newBox = obj->createInlineBox(false, obj == this);
665 KHTMLAssert(newBox->isInlineFlowBox());
666 box = static_cast<InlineFlowBox*>(newBox);
667 box->setFirstLineStyleBit(m_firstLine);
669 // We have a new box. Append it to the inline box we get by constructing our
670 // parent. If we have hit the block itself, then |box| represents the root
671 // inline box for the line, and it doesn't have to be appended to any parent
672 // inline.
673 if (obj != this) {
674 InlineFlowBox* parentBox = createLineBoxes(obj->parent());
675 parentBox->addToLine(box);
679 return box;
682 RootInlineBox* RenderBlock::constructLine(const BidiIterator &/*start*/, const BidiIterator &end)
684 if (!sFirstBidiRun)
685 return 0; // We had no runs. Don't make a root inline box at all. The line is empty.
687 InlineFlowBox* parentBox = 0;
688 for (BidiRun* r = sFirstBidiRun; r; r = r->nextRun) {
689 // Create a box for our object.
690 r->box = r->obj->createInlineBox(r->obj->isPositioned(), false);
692 // If we have no parent box yet, or if the run is not simply a sibling,
693 // then we need to construct inline boxes as necessary to properly enclose the
694 // run's inline box.
695 if (!parentBox || (parentBox->object() != r->obj->parent()))
696 // Create new inline boxes all the way back to the appropriate insertion point.
697 parentBox = createLineBoxes(r->obj->parent());
699 // Append the inline box to this line.
700 parentBox->addToLine(r->box);
703 // We should have a root inline box. It should be unconstructed and
704 // be the last continuation of our line list.
705 KHTMLAssert(lastLineBox() && !lastLineBox()->isConstructed());
707 // Set bits on our inline flow boxes that indicate which sides should
708 // paint borders/margins/padding. This knowledge will ultimately be used when
709 // we determine the horizontal positions and widths of all the inline boxes on
710 // the line.
711 RenderObject* endObject = 0;
712 bool lastLine = !end.obj;
713 if (end.obj && end.pos == 0)
714 endObject = end.obj;
715 lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject);
717 // Now mark the line boxes as being constructed.
718 lastLineBox()->setConstructed();
720 // Return the last line.
721 return lastRootBox();
724 void RenderBlock::computeHorizontalPositionsForLine(InlineFlowBox* lineBox, BidiState &bidi)
726 // First determine our total width.
727 int totWidth = lineBox->getFlowSpacingWidth();
728 BidiRun* r = 0;
729 for (r = sFirstBidiRun; r; r = r->nextRun) {
730 if (r->obj->isPositioned())
731 continue; // Positioned objects are only participating to figure out their
732 // correct static x position. They have no effect on the width.
733 if (r->obj->isText())
734 r->box->setWidth(static_cast<RenderText *>(r->obj)->width(r->start, r->stop-r->start, m_firstLine));
735 else if (!r->obj->isInlineFlow()) {
736 r->obj->calcWidth();
737 r->box->setWidth(r->obj->width());
738 totWidth += r->obj->marginLeft() + r->obj->marginRight();
740 totWidth += r->box->width();
743 // Armed with the total width of the line (without justification),
744 // we now examine our text-align property in order to determine where to position the
745 // objects horizontally. The total width of the line can be increased if we end up
746 // justifying text.
747 int x = leftOffset(m_height);
748 int availableWidth = lineWidth(m_height);
749 switch(style()->textAlign()) {
750 case LEFT:
751 case KHTML_LEFT:
752 if (style()->direction() == RTL && totWidth > availableWidth)
753 x -= (totWidth - availableWidth);
754 numSpaces = 0;
755 break;
756 case JUSTIFY:
757 if (numSpaces != 0 && !bidi.current.atEnd() && !bidi.current.obj->isBR() )
758 break;
759 // fall through
760 case TAAUTO:
761 numSpaces = 0;
762 // for right to left fall through to right aligned
763 if (bidi.context->basicDir == QChar::DirL)
764 break;
765 case RIGHT:
766 case KHTML_RIGHT:
767 if (style()->direction() == RTL || totWidth < availableWidth)
768 x += availableWidth - totWidth;
769 numSpaces = 0;
770 break;
771 case CENTER:
772 case KHTML_CENTER:
773 int xd = (availableWidth - totWidth)/2;
774 x += xd >0 ? xd : 0;
775 numSpaces = 0;
776 break;
779 if (numSpaces > 0) {
780 for (r = sFirstBidiRun; r; r = r->nextRun) {
781 int spaceAdd = 0;
782 if (numSpaces > 0 && r->obj->isText()) {
783 // get the number of spaces in the run
784 int spaces = 0;
785 for ( int i = r->start; i < r->stop; i++ ) {
786 const QChar c = static_cast<RenderText *>(r->obj)->text()[i];
787 if (c.category() == QChar::Separator_Space || c == '\n')
788 spaces++;
791 KHTMLAssert(spaces <= numSpaces);
793 // Only justify text with white-space: normal.
794 if (r->obj->style()->whiteSpace() == NORMAL) {
795 spaceAdd = (availableWidth - totWidth)*spaces/numSpaces;
796 spaceAdd = qMax(0, spaceAdd);
797 static_cast<InlineTextBox*>(r->box)->setSpaceAdd(spaceAdd);
798 totWidth += spaceAdd;
800 numSpaces -= spaces;
805 // The widths of all runs are now known. We can now place every inline box (and
806 // compute accurate widths for the inline flow boxes).
807 int rightPos = lineBox->placeBoxesHorizontally(x);
808 if (rightPos > m_overflowWidth)
809 m_overflowWidth = rightPos; // FIXME: Work for rtl overflow also.
810 if (x < 0)
811 m_overflowLeft = qMin(m_overflowLeft, x);
814 void RenderBlock::computeVerticalPositionsForLine(RootInlineBox* lineBox)
816 lineBox->verticallyAlignBoxes(m_height);
817 lineBox->setBlockHeight(m_height);
819 // Check for page-breaks
820 if (canvas()->pagedMode() && !lineBox->afterPageBreak())
821 // If we get a page-break we might need to redo the line-break
822 if (clearLineOfPageBreaks(lineBox) && hasFloats()) return;
824 // See if the line spilled out. If so set overflow height accordingly.
825 int bottomOfLine = lineBox->bottomOverflow();
826 if (bottomOfLine > m_height && bottomOfLine > m_overflowHeight)
827 m_overflowHeight = bottomOfLine;
829 bool beforeContent = true;
831 // Now make sure we place replaced render objects correctly.
832 for (BidiRun* r = sFirstBidiRun; r; r = r->nextRun) {
834 // For positioned placeholders, cache the static Y position an object with non-inline display would have.
835 // Either it is unchanged if it comes before any real linebox, or it must clear the current line (already accounted in m_height).
836 // This value will be picked up by position() if relevant.
837 if (r->obj->isPositioned())
838 r->box->setYPos( beforeContent && r->obj->isBox() ? static_cast<RenderBox*>(r->obj)->staticY() : m_height );
839 else if (beforeContent)
840 beforeContent = false;
842 // Position is used to properly position both replaced elements and
843 // to update the static normal flow x/y of positioned elements.
844 r->obj->position(r->box, r->start, r->stop - r->start, r->level%2);
848 bool RenderBlock::clearLineOfPageBreaks(InlineFlowBox* lineBox)
850 bool doPageBreak = false;
851 // Check for physical page-breaks
852 int xpage = crossesPageBreak(lineBox->topOverflow(), lineBox->bottomOverflow());
853 if (xpage) {
854 #ifdef PAGE_DEBUG
855 kDebug(6040) << renderName() << " Line crosses to page " << xpage;
856 kDebug(6040) << renderName() << " at pos " << lineBox->yPos() << " height " << lineBox->height();
857 #endif
859 doPageBreak = true;
860 // check page-break-inside
861 if (!style()->pageBreakInside()) {
862 if (parent()->canClear(this, PageBreakNormal)) {
863 setNeedsPageClear(true);
864 doPageBreak = false;
866 #ifdef PAGE_DEBUG
867 else
868 kDebug(6040) << "Ignoring page-break-inside: avoid";
869 #endif
871 // check orphans
872 int orphans = 0;
873 InlineRunBox* box = lineBox->prevLineBox();
874 while (box && orphans < style()->orphans()) {
875 orphans++;
876 box = box->prevLineBox();
879 if (orphans == 0) {
880 setNeedsPageClear(true);
881 doPageBreak = false;
882 } else
883 if (orphans < style()->orphans() ) {
884 #ifdef PAGE_DEBUG
885 kDebug(6040) << "Orphans: " << orphans;
886 #endif
887 // Orphans is a level 2 page-break rule and can be broken only
888 // if the break is physically required.
889 if (parent()->canClear(this, PageBreakHarder)) {
890 // move block instead
891 setNeedsPageClear(true);
892 doPageBreak = false;
894 #ifdef PAGE_DEBUG
895 else
896 kDebug(6040) << "Ignoring violated orphans";
897 #endif
899 if (doPageBreak) {
900 int pTop = pageTopAfter(lineBox->yPos());
902 m_height = pTop;
903 lineBox->setAfterPageBreak(true);
904 lineBox->verticallyAlignBoxes(m_height);
905 if (lineBox->yPos() < pTop) {
906 // ### serious crap. render_line is sometimes placing lines too high
907 kDebug(6040) << "page top overflow by repositioned line";
908 int heightIncrease = pTop - lineBox->yPos();
909 m_height = pTop + heightIncrease;
910 lineBox->verticallyAlignBoxes(m_height);
912 #ifdef PAGE_DEBUG
913 kDebug(6040) << "Cleared line " << lineBox->yPos() - oldYPos << "px";
914 #endif
915 setContainsPageBreak(true);
918 return doPageBreak;
921 // collects one line of the paragraph and transforms it to visual order
922 void RenderBlock::bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi)
924 if ( start == end ) {
925 if ( start.current() == '\n' ) {
926 m_height += lineHeight( m_firstLine );
928 return;
931 #if BIDI_DEBUG > 1
932 kDebug(6041) << "reordering Line from " << start.obj << "/" << start.pos << " to " << end.obj << "/" << end.pos;
933 #endif
935 sFirstBidiRun = 0;
936 sLastBidiRun = 0;
937 sBidiRunCount = 0;
939 // context->ref();
941 dir = QChar::DirON;
942 emptyRun = true;
944 numSpaces = 0;
946 bidi.current = start;
947 bidi.last = bidi.current;
948 bool atEnd = false;
949 while( 1 ) {
950 QChar::Direction dirCurrent;
951 if (atEnd) {
952 //kDebug(6041) << "atEnd";
953 BidiContext *c = bidi.context;
954 if ( bidi.current.atEnd())
955 while ( c->parent )
956 c = c->parent;
957 dirCurrent = c->dir;
959 else if (bidi.context->override) {
960 dirCurrent = bidi.context->dir;
962 else {
963 dirCurrent = bidi.current.direction();
966 #ifndef QT_NO_UNICODETABLES
968 #if BIDI_DEBUG > 1
969 kDebug(6041) << "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;
970 #endif
972 switch(dirCurrent) {
974 // embedding and overrides (X1-X9 in the Bidi specs)
975 case QChar::DirRLE:
976 case QChar::DirLRE:
977 case QChar::DirRLO:
978 case QChar::DirLRO:
979 case QChar::DirPDF:
980 embed( dirCurrent, bidi );
981 break;
983 // strong types
984 case QChar::DirL:
985 if(dir == QChar::DirON)
986 dir = QChar::DirL;
987 switch(bidi.status.last)
989 case QChar::DirL:
990 bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; break;
991 case QChar::DirR:
992 case QChar::DirAL:
993 case QChar::DirEN:
994 case QChar::DirAN:
995 appendRun( bidi );
996 break;
997 case QChar::DirES:
998 case QChar::DirET:
999 case QChar::DirCS:
1000 case QChar::DirBN:
1001 case QChar::DirB:
1002 case QChar::DirS:
1003 case QChar::DirWS:
1004 case QChar::DirON:
1005 if( bidi.status.eor != QChar::DirL ) {
1006 //last stuff takes embedding dir
1007 if(bidi.context->dir == QChar::DirL || bidi.status.lastStrong == QChar::DirL) {
1008 if ( bidi.status.eor != QChar::DirEN && bidi.status.eor != QChar::DirAN && bidi.status.eor != QChar::DirON )
1009 appendRun( bidi );
1010 dir = QChar::DirL;
1011 bidi.eor = bidi.current;
1012 bidi.status.eor = QChar::DirL;
1013 } else {
1014 if ( bidi.status.eor == QChar::DirEN || bidi.status.eor == QChar::DirAN )
1016 dir = bidi.status.eor;
1017 appendRun( bidi );
1019 dir = QChar::DirR;
1020 bidi.eor = bidi.last;
1021 appendRun( bidi );
1022 dir = QChar::DirL;
1023 bidi.status.eor = QChar::DirL;
1025 } else {
1026 bidi.eor = bidi.current; bidi.status.eor = QChar::DirL;
1028 default:
1029 break;
1031 bidi.status.lastStrong = QChar::DirL;
1032 break;
1033 case QChar::DirAL:
1034 case QChar::DirR:
1035 if(dir == QChar::DirON) dir = QChar::DirR;
1036 switch(bidi.status.last)
1038 case QChar::DirR:
1039 case QChar::DirAL:
1040 bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; break;
1041 case QChar::DirL:
1042 case QChar::DirEN:
1043 case QChar::DirAN:
1044 appendRun( bidi );
1045 dir = QChar::DirR;
1046 bidi.eor = bidi.current;
1047 bidi.status.eor = QChar::DirR;
1048 break;
1049 case QChar::DirES:
1050 case QChar::DirET:
1051 case QChar::DirCS:
1052 case QChar::DirBN:
1053 case QChar::DirB:
1054 case QChar::DirS:
1055 case QChar::DirWS:
1056 case QChar::DirON:
1057 if( !(bidi.status.eor == QChar::DirR) && !(bidi.status.eor == QChar::DirAL) ) {
1058 //last stuff takes embedding dir
1059 if(bidi.context->dir == QChar::DirR || bidi.status.lastStrong == QChar::DirR
1060 || bidi.status.lastStrong == QChar::DirAL) {
1061 appendRun( bidi );
1062 dir = QChar::DirR;
1063 bidi.eor = bidi.current;
1064 bidi.status.eor = QChar::DirR;
1065 } else {
1066 dir = QChar::DirL;
1067 bidi.eor = bidi.last;
1068 appendRun( bidi );
1069 dir = QChar::DirR;
1070 bidi.status.eor = QChar::DirR;
1072 } else {
1073 bidi.eor = bidi.current; bidi.status.eor = QChar::DirR;
1075 default:
1076 break;
1078 bidi.status.lastStrong = dirCurrent;
1079 break;
1081 // weak types:
1083 case QChar::DirNSM:
1084 // ### if @sor, set dir to dirSor
1085 break;
1086 case QChar::DirEN:
1087 if(!(bidi.status.lastStrong == QChar::DirAL)) {
1088 // if last strong was AL change EN to AN
1089 if(dir == QChar::DirON) {
1090 dir = QChar::DirL;
1092 switch(bidi.status.last)
1094 case QChar::DirET:
1095 if ( bidi.status.lastStrong == QChar::DirR || bidi.status.lastStrong == QChar::DirAL ) {
1096 appendRun( bidi );
1097 dir = QChar::DirEN;
1098 bidi.status.eor = QChar::DirEN;
1100 // fall through
1101 case QChar::DirEN:
1102 case QChar::DirL:
1103 bidi.eor = bidi.current;
1104 bidi.status.eor = dirCurrent;
1105 break;
1106 case QChar::DirR:
1107 case QChar::DirAL:
1108 case QChar::DirAN:
1109 appendRun( bidi );
1110 bidi.status.eor = QChar::DirEN;
1111 dir = QChar::DirEN; break;
1112 case QChar::DirES:
1113 case QChar::DirCS:
1114 if(bidi.status.eor == QChar::DirEN) {
1115 bidi.eor = bidi.current; break;
1117 case QChar::DirBN:
1118 case QChar::DirB:
1119 case QChar::DirS:
1120 case QChar::DirWS:
1121 case QChar::DirON:
1122 if(bidi.status.eor == QChar::DirR) {
1123 // neutrals go to R
1124 bidi.eor = bidi.last;
1125 appendRun( bidi );
1126 dir = QChar::DirEN;
1127 bidi.status.eor = QChar::DirEN;
1129 else if( bidi.status.eor == QChar::DirL ||
1130 (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) {
1131 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1132 } else {
1133 // numbers on both sides, neutrals get right to left direction
1134 if(dir != QChar::DirL) {
1135 appendRun( bidi );
1136 bidi.eor = bidi.last;
1137 dir = QChar::DirR;
1138 appendRun( bidi );
1139 dir = QChar::DirEN;
1140 bidi.status.eor = QChar::DirEN;
1141 } else {
1142 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1145 default:
1146 break;
1148 break;
1150 case QChar::DirAN:
1151 dirCurrent = QChar::DirAN;
1152 if(dir == QChar::DirON) dir = QChar::DirAN;
1153 switch(bidi.status.last)
1155 case QChar::DirL:
1156 case QChar::DirAN:
1157 bidi.eor = bidi.current; bidi.status.eor = QChar::DirAN; break;
1158 case QChar::DirR:
1159 case QChar::DirAL:
1160 case QChar::DirEN:
1161 appendRun( bidi );
1162 dir = QChar::DirAN; bidi.status.eor = QChar::DirAN;
1163 break;
1164 case QChar::DirCS:
1165 if(bidi.status.eor == QChar::DirAN) {
1166 bidi.eor = bidi.current; break;
1168 case QChar::DirES:
1169 case QChar::DirET:
1170 case QChar::DirBN:
1171 case QChar::DirB:
1172 case QChar::DirS:
1173 case QChar::DirWS:
1174 case QChar::DirON:
1175 if(bidi.status.eor == QChar::DirR) {
1176 // neutrals go to R
1177 bidi.eor = bidi.last;
1178 appendRun( bidi );
1179 dir = QChar::DirAN;
1180 bidi.status.eor = QChar::DirAN;
1181 } else if( bidi.status.eor == QChar::DirL ||
1182 (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) {
1183 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1184 } else {
1185 // numbers on both sides, neutrals get right to left direction
1186 if(dir != QChar::DirL) {
1187 appendRun( bidi );
1188 bidi.eor = bidi.last;
1189 dir = QChar::DirR;
1190 appendRun( bidi );
1191 dir = QChar::DirAN;
1192 bidi.status.eor = QChar::DirAN;
1193 } else {
1194 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1197 default:
1198 break;
1200 break;
1201 case QChar::DirES:
1202 case QChar::DirCS:
1203 break;
1204 case QChar::DirET:
1205 if(bidi.status.last == QChar::DirEN) {
1206 dirCurrent = QChar::DirEN;
1207 bidi.eor = bidi.current; bidi.status.eor = dirCurrent;
1208 break;
1210 break;
1212 // boundary neutrals should be ignored
1213 case QChar::DirBN:
1214 break;
1215 // neutrals
1216 case QChar::DirB:
1217 // ### what do we do with newline and paragraph seperators that come to here?
1218 break;
1219 case QChar::DirS:
1220 // ### implement rule L1
1221 break;
1222 case QChar::DirWS:
1223 break;
1224 case QChar::DirON:
1225 break;
1226 default:
1227 break;
1230 //cout << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << endl;
1232 if(bidi.current.atEnd()) break;
1234 // set status.last as needed.
1235 switch(dirCurrent)
1237 case QChar::DirET:
1238 case QChar::DirES:
1239 case QChar::DirCS:
1240 case QChar::DirS:
1241 case QChar::DirWS:
1242 case QChar::DirON:
1243 switch(bidi.status.last)
1245 case QChar::DirL:
1246 case QChar::DirR:
1247 case QChar::DirAL:
1248 case QChar::DirEN:
1249 case QChar::DirAN:
1250 bidi.status.last = dirCurrent;
1251 break;
1252 default:
1253 bidi.status.last = QChar::DirON;
1255 break;
1256 case QChar::DirNSM:
1257 case QChar::DirBN:
1258 // ignore these
1259 break;
1260 case QChar::DirEN:
1261 if ( bidi.status.last == QChar::DirL ) {
1262 break;
1264 // fall through
1265 default:
1266 bidi.status.last = dirCurrent;
1268 #endif
1270 if ( atEnd ) break;
1271 bidi.last = bidi.current;
1273 if ( emptyRun ) {
1274 bidi.sor = bidi.current;
1275 bidi.eor = bidi.current;
1276 emptyRun = false;
1279 // this causes the operator ++ to open and close embedding levels as needed
1280 // for the CSS unicode-bidi property
1281 bidi.current.increment( &bidi );
1283 if ( bidi.current == end ) {
1284 if ( emptyRun )
1285 break;
1286 atEnd = true;
1290 #if BIDI_DEBUG > 0
1291 kDebug(6041) << "reached end of line current=" << bidi.current.obj << "/" << bidi.current.pos
1292 << ", eor=" << bidi.eor.obj << "/" << bidi.eor.pos << endl;
1293 #endif
1294 if ( !emptyRun && bidi.sor != bidi.current ) {
1295 bidi.eor = bidi.last;
1296 appendRun( bidi );
1299 // reorder line according to run structure...
1301 // first find highest and lowest levels
1302 uchar levelLow = 128;
1303 uchar levelHigh = 0;
1304 BidiRun *r = sFirstBidiRun;
1305 while ( r ) {
1306 if ( r->level > levelHigh )
1307 levelHigh = r->level;
1308 if ( r->level < levelLow )
1309 levelLow = r->level;
1310 r = r->nextRun;
1313 // implements reordering of the line (L2 according to Bidi spec):
1314 // L2. From the highest level found in the text to the lowest odd level on each line,
1315 // reverse any contiguous sequence of characters that are at that level or higher.
1317 // reversing is only done up to the lowest odd level
1318 if( !(levelLow%2) ) levelLow++;
1320 int count = sBidiRunCount - 1;
1322 // do not reverse for visually ordered web sites
1323 if(!style()->visuallyOrdered()) {
1324 while(levelHigh >= levelLow) {
1325 int i = 0;
1326 BidiRun* currRun = sFirstBidiRun;
1327 while ( i < count ) {
1328 while(i < count && currRun && currRun->level < levelHigh) {
1329 i++;
1330 currRun = currRun->nextRun;
1332 int start = i;
1333 while(i <= count && currRun && currRun->level >= levelHigh) {
1334 i++;
1335 currRun = currRun->nextRun;
1337 int end = i-1;
1338 reverseRuns(start, end);
1340 levelHigh--;
1344 #if BIDI_DEBUG > 0
1345 kDebug(6041) << "visual order is:";
1346 for (BidiRun* curr = sFirstBidiRun; curr; curr = curr->nextRun)
1347 kDebug(6041) << " " << curr;
1348 #endif
1351 void RenderBlock::layoutInlineChildren(bool relayoutChildren, int breakBeforeLine)
1353 BidiState bidi;
1355 m_overflowHeight = 0;
1357 invalidateVerticalPosition();
1358 #ifdef DEBUG_LAYOUT
1359 QTime qt;
1360 qt.start();
1361 kDebug( 6040 ) << renderName() << " layoutInlineChildren( " << this <<" )";
1362 #endif
1363 #if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS )
1364 kDebug(6041) << " ------- bidi start " << this << " -------";
1365 #endif
1367 m_height = borderTop() + paddingTop();
1368 int toAdd = borderBottom() + paddingBottom();
1369 if (m_layer && scrollsOverflowX() && style()->height().isVariable())
1370 toAdd += m_layer->horizontalScrollbarHeight();
1372 // Figure out if we should clear our line boxes.
1373 bool fullLayout = !firstLineBox() || !firstChild() || selfNeedsLayout() || relayoutChildren || hasFloats();
1375 if (fullLayout)
1376 deleteInlineBoxes();
1378 // Text truncation only kicks in if your overflow isn't visible and your
1379 // text-overflow-mode isn't clip.
1380 bool hasTextOverflow = style()->textOverflow() && hasOverflowClip();
1382 // Walk all the lines and delete our ellipsis line boxes if they exist.
1383 if (hasTextOverflow)
1384 deleteEllipsisLineBoxes();
1386 if (firstChild()) {
1387 // layout replaced elements
1388 RenderObject *o = first( this, 0, false );
1389 while ( o ) {
1390 invalidateVerticalPosition();
1391 if (o->markedForRepaint()) {
1392 o->repaintDuringLayout();
1393 o->setMarkedForRepaint(false);
1395 if (o->isReplaced() || o->isFloating() || o->isPositioned()) {
1397 if ((!o->isPositioned() || o->isPosWithStaticDim()) &&
1398 (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent()))
1399 o->setChildNeedsLayout(true, false);
1401 if (o->isPositioned()) {
1402 if (!o->inPosObjectList())
1403 o->containingBlock()->insertPositionedObject(o);
1404 if (fullLayout)
1405 static_cast<RenderBox*>(o)->RenderBox::deleteInlineBoxes();
1406 } else {
1407 if (fullLayout || o->needsLayout())
1408 static_cast<RenderBox*>(o)->RenderBox::dirtyInlineBoxes(fullLayout);
1409 o->layoutIfNeeded();
1412 else {
1413 if (fullLayout || o->selfNeedsLayout())
1414 o->dirtyInlineBoxes(fullLayout);
1415 o->setNeedsLayout(false);
1417 o = Bidinext( this, o, 0, false );
1420 BidiContext *startEmbed;
1421 if( style()->direction() == LTR ) {
1422 startEmbed = new BidiContext( 0, QChar::DirL );
1423 bidi.status.eor = QChar::DirL;
1424 } else {
1425 startEmbed = new BidiContext( 1, QChar::DirR );
1426 bidi.status.eor = QChar::DirR;
1428 startEmbed->ref();
1430 bidi.status.lastStrong = QChar::DirON;
1431 bidi.status.last = QChar::DirON;
1433 bidi.context = startEmbed;
1435 // We want to skip ahead to the first dirty line
1436 BidiIterator start;
1437 RootInlineBox* startLine = determineStartPosition(fullLayout, start, bidi);
1439 // Then look forward to see if we can find a clean area that is clean up to the end.
1440 BidiIterator cleanLineStart;
1441 BidiStatus cleanLineBidiStatus;
1442 BidiContext* cleanLineBidiContext = 0;
1443 int endLineYPos = 0;
1444 RootInlineBox* endLine = (fullLayout || !startLine) ?
1445 0 : determineEndPosition(startLine, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLineYPos);
1447 // Extract the clean area. We will add it back if we determine that we're able to
1448 // synchronize after relayouting the dirty area.
1449 if (endLine)
1450 for (RootInlineBox* line = endLine; line; line = line->nextRootBox())
1451 line->extractLine();
1453 // Delete the dirty area.
1454 if (startLine) {
1455 RenderArena* arena = renderArena();
1456 RootInlineBox* box = startLine;
1457 while (box) {
1458 RootInlineBox* next = box->nextRootBox();
1459 box->deleteLine(arena);
1460 box = next;
1462 startLine = 0;
1464 BidiIterator end = start;
1465 bool endLineMatched = false;
1466 m_firstLine = true;
1468 if (!smidpoints)
1469 smidpoints = new QVector<BidiIterator>;
1471 sNumMidpoints = 0;
1472 sCurrMidpoint = 0;
1473 sCompactFirstBidiRun = sCompactLastBidiRun = 0;
1474 sCompactBidiRunCount = 0;
1476 previousLineBrokeAtBR = true;
1478 int lineCount = 0;
1479 bool pagebreakHint = false;
1480 int oldPos = 0;
1481 BidiIterator oldStart;
1482 BidiState oldBidi;
1483 const bool pagedMode = canvas()->pagedMode();
1485 while( !end.atEnd() ) {
1486 start = end;
1487 if (endLine && (endLineMatched = matchedEndLine(start, bidi.status, bidi.context, cleanLineStart, cleanLineBidiStatus, cleanLineBidiContext, endLine, endLineYPos)))
1488 break;
1489 lineCount++;
1490 betweenMidpoints = false;
1491 isLineEmpty = true;
1492 pagebreakHint = false;
1493 if (pagedMode) {
1494 oldPos = m_height;
1495 oldStart = start;
1496 oldBidi = bidi;
1498 if (lineCount == breakBeforeLine) {
1499 m_height = pageTopAfter(oldPos);
1500 pagebreakHint = true;
1502 redo_linebreak:
1503 end = findNextLineBreak(start, bidi);
1504 if( start.atEnd() ) {
1505 deleteBidiRuns(renderArena());
1506 break;
1508 if (!isLineEmpty) {
1509 bidiReorderLine(start, end, bidi);
1511 // Now that the runs have been ordered, we create the line boxes.
1512 // At the same time we figure out where border/padding/margin should be applied for
1513 // inline flow boxes.
1515 RootInlineBox* lineBox = 0;
1516 if (sBidiRunCount) {
1517 lineBox = constructLine(start, end);
1518 if (lineBox) {
1519 lineBox->setEndsWithBreak(previousLineBrokeAtBR);
1520 if (pagebreakHint) lineBox->setAfterPageBreak(true);
1522 // Now we position all of our text runs horizontally.
1523 computeHorizontalPositionsForLine(lineBox, bidi);
1525 // Now position our text runs vertically.
1526 computeVerticalPositionsForLine(lineBox);
1528 // SVG
1529 if (lineBox->isSVGRootInlineBox()) {
1530 //kDebug() << "svgrootinline box:" << endl;
1531 WebCore::SVGRootInlineBox* svgLineBox = static_cast<WebCore::SVGRootInlineBox*>(lineBox);
1532 svgLineBox->computePerCharacterLayoutInformation();
1535 deleteBidiRuns(renderArena());
1537 if (lineBox->afterPageBreak() && hasFloats() && !pagebreakHint) {
1538 start = end = oldStart;
1539 bidi = oldBidi;
1540 m_height = pageTopAfter(oldPos);
1541 deleteLastLineBox(renderArena());
1542 pagebreakHint = true;
1543 goto redo_linebreak;
1548 if( end == start || (end.obj && end.obj->isBR() && !start.obj->isBR() ) ) {
1549 end.increment(&bidi);
1550 } else if (end.obj && end.obj->style()->preserveLF() && end.current() == QChar('\n')) {
1551 end.increment(&bidi);
1554 if (lineBox)
1555 lineBox->setLineBreakInfo(end.obj, end.pos, bidi.status, bidi.context);
1557 m_firstLine = false;
1558 newLine();
1561 sNumMidpoints = 0;
1562 sCurrMidpoint = 0;
1563 sCompactFirstBidiRun = sCompactLastBidiRun = 0;
1564 sCompactBidiRunCount = 0;
1566 startEmbed->deref();
1567 //embed->deref();
1569 if (endLine) {
1570 if (endLineMatched) {
1571 // Attach all the remaining lines, and then adjust their y-positions as needed.
1572 for (RootInlineBox* line = endLine; line; line = line->nextRootBox())
1573 line->attachLine();
1575 // Now apply the offset to each line if needed.
1576 int delta = m_height - endLineYPos;
1577 if (delta) {
1578 for (RootInlineBox* line = endLine; line; line = line->nextRootBox())
1579 line->adjustPosition(0, delta);
1581 m_height = lastRootBox()->blockHeight();
1582 } else {
1583 // Delete all the remaining lines.
1584 InlineRunBox* line = endLine;
1585 RenderArena* arena = renderArena();
1586 while (line) {
1587 InlineRunBox* next = line->nextLineBox();
1588 line->deleteLine(arena);
1589 line = next;
1595 sNumMidpoints = 0;
1596 sCurrMidpoint = 0;
1599 // If we violate widows page-breaking rules, we set a hint and relayout.
1600 // Note that the widows rule might still be violated afterwards if the lines have become wider
1601 if (canvas()->pagedMode() && containsPageBreak() && breakBeforeLine == 0)
1603 int orphans = 0;
1604 int widows = 0;
1605 // find breaking line
1606 InlineRunBox* lineBox = firstLineBox();
1607 while (lineBox) {
1608 if (lineBox->isInlineFlowBox()) {
1609 InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(lineBox);
1610 if (flowBox->afterPageBreak()) break;
1612 orphans++;
1613 lineBox = lineBox->nextLineBox();
1615 InlineFlowBox* pageBreaker = static_cast<InlineFlowBox*>(lineBox);
1616 if (!pageBreaker) goto no_break;
1617 // count widows
1618 while (lineBox && widows < style()->widows()) {
1619 if (lineBox->hasTextChildren())
1620 widows++;
1621 lineBox = lineBox->nextLineBox();
1623 // Widows rule broken and more orphans left to use
1624 if (widows < style()->widows() && orphans > 0) {
1625 kDebug( 6040 ) << "Widows: " << widows;
1626 // Check if we have enough orphans after respecting widows count
1627 int newOrphans = orphans - (style()->widows() - widows);
1628 if (newOrphans < style()->orphans()) {
1629 if (parent()->canClear(this,PageBreakHarder)) {
1630 // Relayout to remove incorrect page-break
1631 setNeedsPageClear(true);
1632 setContainsPageBreak(false);
1633 layoutInlineChildren(relayoutChildren, -1);
1634 return;
1636 } else {
1637 // Set hint and try again
1638 layoutInlineChildren(relayoutChildren, newOrphans+1);
1639 return;
1643 no_break:
1645 // in case we have a float on the last line, it might not be positioned up to now.
1646 // This has to be done before adding in the bottom border/padding, or the float will
1647 // include the padding incorrectly. -dwh
1648 positionNewFloats();
1650 // Now add in the bottom border/padding.
1651 m_height += toAdd;
1653 // Always make sure this is at least our height.
1654 m_overflowHeight = qMax(m_height, m_overflowHeight);
1656 // See if any lines spill out of the block. If so, we need to update our overflow width.
1657 checkLinesForOverflow();
1659 // See if we have any lines that spill out of our block. If we do, then we will
1660 // possibly need to truncate text.
1661 if (hasTextOverflow)
1662 checkLinesForTextOverflow();
1664 #if BIDI_DEBUG > 1
1665 kDebug(6041) << " ------- bidi end " << this << " -------";
1666 #endif
1667 //kDebug() << "RenderBlock::layoutInlineChildren time used " << qt.elapsed();
1668 //kDebug(6040) << "height = " << m_height;
1671 RootInlineBox* RenderBlock::determineStartPosition(bool fullLayout, BidiIterator& start, BidiState& bidi)
1673 RootInlineBox* curr = 0;
1674 RootInlineBox* last = 0;
1675 RenderObject* startObj = 0;
1676 int pos = 0;
1678 if (fullLayout) {
1679 // Nuke all our lines.
1680 // ### should be done already at this point... assert( !firstRootBox() )
1681 if (firstRootBox()) {
1682 RenderArena* arena = renderArena();
1683 curr = firstRootBox();
1684 while (curr) {
1685 RootInlineBox* next = curr->nextRootBox();
1686 curr->deleteLine(arena);
1687 curr = next;
1689 assert(!firstLineBox() && !lastLineBox());
1691 } else {
1692 int cnt = 0;
1693 for (curr = firstRootBox(); curr && !curr->isDirty(); curr = curr->nextRootBox()) cnt++;
1694 if (curr) {
1695 // kDebug( 6040 ) << "found dirty line at " << cnt;
1696 // We have a dirty line.
1697 if (RootInlineBox* prevRootBox = curr->prevRootBox()) {
1698 // We have a previous line.
1699 if (!prevRootBox->endsWithBreak() || prevRootBox->lineBreakObj()->isText() && prevRootBox->lineBreakPos() >= static_cast<RenderText*>(prevRootBox->lineBreakObj())->stringLength())
1700 // The previous line didn't break cleanly or broke at a newline
1701 // that has been deleted, so treat it as dirty too.
1702 curr = prevRootBox;
1704 } else {
1705 // kDebug( 6040 ) << "No dirty line found";
1706 // No dirty lines were found.
1707 // If the last line didn't break cleanly, treat it as dirty.
1708 if (lastRootBox() && !lastRootBox()->endsWithBreak())
1709 curr = lastRootBox();
1712 // If we have no dirty lines, then last is just the last root box.
1713 last = curr ? curr->prevRootBox() : lastRootBox();
1716 m_firstLine = !last;
1717 previousLineBrokeAtBR = !last || last->endsWithBreak();
1718 if (last) {
1719 m_height = last->blockHeight();
1720 startObj = last->lineBreakObj();
1721 pos = last->lineBreakPos();
1722 bidi.status = last->lineBreakBidiStatus();
1723 } else {
1724 startObj = first(this, &bidi, false);
1727 start = BidiIterator(this, startObj, pos);
1729 return curr;
1732 RootInlineBox* RenderBlock::determineEndPosition(RootInlineBox* startLine, BidiIterator& cleanLineStart, BidiStatus& cleanLineBidiStatus, BidiContext* cleanLineBidiContext, int& yPos)
1734 RootInlineBox* last = 0;
1735 if (!startLine)
1736 last = 0;
1737 else {
1738 for (RootInlineBox* curr = startLine->nextRootBox(); curr; curr = curr->nextRootBox()) {
1739 if (curr->isDirty())
1740 last = 0;
1741 else if (!last)
1742 last = curr;
1746 if (!last)
1747 return 0;
1749 RootInlineBox* prev = last->prevRootBox();
1750 cleanLineStart = BidiIterator(this, prev->lineBreakObj(), prev->lineBreakPos());
1751 cleanLineBidiStatus = prev->lineBreakBidiStatus();
1752 cleanLineBidiContext = prev->lineBreakBidiContext();
1753 yPos = prev->blockHeight();
1755 return last;
1758 bool RenderBlock::matchedEndLine(const BidiIterator& start, const BidiStatus& status, BidiContext* context,
1759 const BidiIterator& endLineStart, const BidiStatus& endLineStatus, BidiContext* endLineContext,
1760 RootInlineBox*& endLine, int& endYPos)
1762 if (start == endLineStart)
1763 return status == endLineStatus && endLineContext && (*context == *endLineContext);
1764 else {
1765 // The first clean line doesn't match, but we can check a handful of following lines to try
1766 // to match back up.
1767 static int numLines = 8; // The # of lines we're willing to match against.
1768 RootInlineBox* line = endLine;
1769 for (int i = 0; i < numLines && line; i++, line = line->nextRootBox()) {
1770 if (line->lineBreakObj() == start.obj && line->lineBreakPos() == start.pos) {
1771 // We have a match.
1772 if ((line->lineBreakBidiStatus() != status) || (line->lineBreakBidiContext() != context))
1773 return false; // ...but the bidi state doesn't match.
1774 RootInlineBox* result = line->nextRootBox();
1776 // Set our yPos to be the block height of endLine.
1777 if (result)
1778 endYPos = line->blockHeight();
1780 // Now delete the lines that we failed to sync.
1781 RootInlineBox* boxToDelete = endLine;
1782 RenderArena* arena = renderArena();
1783 while (boxToDelete && boxToDelete != result) {
1784 RootInlineBox* next = boxToDelete->nextRootBox();
1785 boxToDelete->deleteLine(arena);
1786 boxToDelete = next;
1789 endLine = result;
1790 return result;
1794 return false;
1797 static void setStaticPosition( RenderBlock* p, RenderObject *o, bool *needToSetStaticX = 0, bool *needToSetStaticY = 0 )
1799 // If our original display wasn't an inline type, then we can
1800 // determine our static x position now.
1801 bool nssx, nssy;
1802 bool isInlineType = o->style()->isOriginalDisplayInlineType();
1803 nssx = o->hasStaticX();
1804 if (nssx && o->isBox()) {
1805 static_cast<RenderBox*>(o)->setStaticX(o->parent()->style()->direction() == LTR ?
1806 p->borderLeft()+p->paddingLeft() :
1807 p->borderRight()+p->paddingRight());
1808 nssx = isInlineType;
1811 // If our original display was an INLINE type, then we can
1812 // determine our static y position now.
1813 nssy = o->hasStaticY();
1814 if (nssy && o->isBox()) {
1815 static_cast<RenderBox*>(o)->setStaticY(p->height());
1816 nssy = !isInlineType;
1818 if (needToSetStaticX) *needToSetStaticX = nssx;
1819 if (needToSetStaticY) *needToSetStaticY = nssy;
1822 static inline bool requiresLineBox(BidiIterator& it)
1824 if (it.obj->isFloatingOrPositioned())
1825 return false;
1826 if (it.obj->isInlineFlow())
1827 return (getBorderPaddingMargin(it.obj, it.endOfInline) != 0);
1828 if (it.obj->style()->preserveWS() || it.obj->isBR())
1829 return true;
1831 switch (it.current().unicode()) {
1832 case 0x0009: // ASCII tab
1833 case 0x000A: // ASCII line feed
1834 case 0x000C: // ASCII form feed
1835 case 0x0020: // ASCII space
1836 case 0x200B: // Zero-width space
1837 return false;
1839 return true;
1842 bool RenderBlock::inlineChildNeedsLineBox(RenderObject* inlineObj) // WC: generatesLineBoxesForInlineChild
1844 assert(inlineObj->parent() == this);
1846 BidiIterator it(this, inlineObj, 0);
1847 while (!it.atEnd() && !requiresLineBox(it))
1848 it.increment(0, false /*skipInlines*/);
1850 return !it.atEnd();
1853 BidiIterator RenderBlock::findNextLineBreak(BidiIterator &start, BidiState &bidi)
1855 int width = lineWidth(m_height);
1856 int w = 0;
1857 int tmpW = 0;
1858 #ifdef DEBUG_LINEBREAKS
1859 kDebug(6041) << "findNextLineBreak: line at " << m_height << " line width " << width;
1860 kDebug(6041) << "sol: " << start.obj << " " << start.pos;
1861 #endif
1863 BidiIterator posStart = start;
1864 bool hadPosStart = false;
1866 // Skip initial whitespace
1867 while (!start.atEnd() && !requiresLineBox(start)) {
1868 if( start.obj->isFloating() || start.obj->isPosWithStaticDim()) {
1869 RenderObject *o = start.obj;
1870 // add to special objects...
1871 if (o->isFloating()) {
1872 insertFloatingObject(o);
1873 positionNewFloats();
1874 width = lineWidth(m_height);
1876 else if (o->isPositioned()) {
1877 // add midpoints to have positioned objects at the correct static location
1878 // while still skipping initial whitespace.
1879 if (!hadPosStart) {
1880 hadPosStart = true;
1881 posStart = start;
1882 // include this object then stop
1883 addMidpoint(BidiIterator(0, o, 0));
1884 } else {
1885 // start/stop
1886 addMidpoint(BidiIterator(0, o, 0));
1887 addMidpoint(BidiIterator(0, o, 0));
1889 setStaticPosition(this, o);
1892 start.increment(&bidi, false /*skipInlines*/);
1895 if (hadPosStart && !start.atEnd())
1896 addMidpoint(start);
1898 if ( start.atEnd() ){
1899 if (hadPosStart) {
1900 start = posStart;
1901 posStart.increment();
1902 return posStart;
1904 return start;
1907 // This variable says we have encountered an object after which initial whitespace should be ignored (e.g. InlineFlows at the begining of a line).
1908 // Either we have nothing to do, if there is no whitespace after the object... or we have to enter the ignoringSpaces state.
1909 // This dilemma will be resolved when we have a peek at the next object.
1910 bool checkShouldIgnoreInitialWhitespace = false;
1912 // This variable is used only if whitespace isn't set to PRE, and it tells us whether
1913 // or not we are currently ignoring whitespace.
1914 bool ignoringSpaces = false;
1915 BidiIterator ignoreStart;
1917 // This variable tracks whether the very last character we saw was a space. We use
1918 // this to detect when we encounter a second space so we know we have to terminate
1919 // a run.
1920 bool currentCharacterIsSpace = false;
1922 RenderObject* trailingSpaceObject = 0;
1924 BidiIterator lBreak = start;
1925 InlineMinMaxIterator it(start.par, start.obj, start.endOfInline, false /*skipPositioned*/);
1926 InlineMinMaxIterator lastIt = it;
1927 int pos = start.pos;
1929 bool prevLineBrokeCleanly = previousLineBrokeAtBR;
1930 previousLineBrokeAtBR = false;
1932 RenderObject* o = it.current;
1933 while( o ) {
1934 #ifdef DEBUG_LINEBREAKS
1935 kDebug(6041) << "new object "<< o <<" width = " << w <<" tmpw = " << tmpW;
1936 #endif
1937 if(o->isBR()) {
1938 if( w + tmpW <= width ) {
1939 lBreak.obj = o;
1940 lBreak.pos = 0;
1941 lBreak.endOfInline = it.endOfInline;
1943 // A <br> always breaks a line, so don't let the line be collapsed
1944 // away. Also, the space at the end of a line with a <br> does not
1945 // get collapsed away. It only does this if the previous line broke
1946 // cleanly. Otherwise the <br> has no effect on whether the line is
1947 // empty or not.
1948 if (prevLineBrokeCleanly)
1949 isLineEmpty = false;
1950 trailingSpaceObject = 0;
1951 previousLineBrokeAtBR = true;
1953 if (!isLineEmpty) {
1954 // only check the clear status for non-empty lines.
1955 EClear clear = o->style()->clear();
1956 if(clear != CNONE)
1957 m_clearStatus = (EClear) (m_clearStatus | clear);
1960 goto end;
1962 if( o->isFloatingOrPositioned() ) {
1963 // add to special objects...
1964 if(o->isFloating()) {
1965 insertFloatingObject(o);
1966 // check if it fits in the current line.
1967 // If it does, position it now, otherwise, position
1968 // it after moving to next line (in newLine() func)
1969 if (o->width()+o->marginLeft()+o->marginRight()+w+tmpW <= width) {
1970 positionNewFloats();
1971 width = lineWidth(m_height);
1974 else if (o->isPositioned() && o->isPosWithStaticDim()) {
1975 bool needToSetStaticX;
1976 bool needToSetStaticY;
1977 setStaticPosition(this, o, &needToSetStaticX, &needToSetStaticY);
1979 // If we're ignoring spaces, we have to stop and include this object and
1980 // then start ignoring spaces again.
1981 if (needToSetStaticX || needToSetStaticY) {
1982 trailingSpaceObject = 0;
1983 ignoreStart.obj = o;
1984 ignoreStart.pos = 0;
1985 if (ignoringSpaces) {
1986 addMidpoint(ignoreStart); // Stop ignoring spaces.
1987 addMidpoint(ignoreStart); // Start ignoring again.
1991 } else if (o->isInlineFlow()) {
1992 tmpW += getBorderPaddingMargin(o, it.endOfInline);
1993 if (isLineEmpty) isLineEmpty = !tmpW;
1994 if (o->isWordBreak()) { // #### shouldn't be an InlineFlow!
1995 w += tmpW;
1996 tmpW = 0;
1997 lBreak.obj = o;
1998 lBreak.pos = 0;
1999 lBreak.endOfInline = it.endOfInline;
2000 } else if (!it.endOfInline) {
2001 // this is the beginning of the line (other non-initial inline flows are handled directly when
2002 // incrementing the iterator below). We want to skip initial whitespace as much as possible.
2003 checkShouldIgnoreInitialWhitespace = true;
2005 } else if ( o->isReplaced() || o->isGlyph() ) {
2006 EWhiteSpace currWS = o->style()->whiteSpace();
2007 EWhiteSpace lastWS = lastIt.current->style()->whiteSpace();
2009 // WinIE marquees have different whitespace characteristics by default when viewed from
2010 // the outside vs. the inside. Text inside is NOWRAP, and so we altered the marquee's
2011 // style to reflect this, but we now have to get back to the original whitespace value
2012 // for the marquee when checking for line breaking.
2013 if (o->isHTMLMarquee() && o->layer() && o->layer()->marquee())
2014 currWS = o->layer()->marquee()->whiteSpace();
2015 if (lastIt.current->isHTMLMarquee() && lastIt.current->layer() && lastIt.current->layer()->marquee())
2016 lastWS = lastIt.current->layer()->marquee()->whiteSpace();
2018 // Break on replaced elements if either has normal white-space.
2019 if (currWS == NORMAL || lastWS == NORMAL) {
2020 w += tmpW;
2021 tmpW = 0;
2022 lBreak.obj = o;
2023 lBreak.pos = 0;
2024 lBreak.endOfInline = false;
2027 tmpW += o->width()+o->marginLeft()+o->marginRight();
2028 if (ignoringSpaces) {
2029 BidiIterator startMid( 0, o, 0 );
2030 addMidpoint(startMid);
2032 isLineEmpty = false;
2033 ignoringSpaces = false;
2034 currentCharacterIsSpace = false;
2035 trailingSpaceObject = 0;
2037 if (o->isListMarker() && o->style()->listStylePosition() == OUTSIDE) {
2038 checkShouldIgnoreInitialWhitespace = true;
2040 } else if ( o->isText() ) {
2041 RenderText *t = static_cast<RenderText *>(o);
2042 int strlen = t->stringLength();
2043 int len = strlen - pos;
2044 QChar *str = t->text();
2046 const Font *f = t->htmlFont( m_firstLine );
2047 // proportional font, needs a bit more work.
2048 int lastSpace = pos;
2049 bool autoWrap = o->style()->autoWrap();
2050 bool preserveWS = o->style()->preserveWS();
2051 bool preserveLF = o->style()->preserveLF();
2052 #ifdef APPLE_CHANGES
2053 int wordSpacing = o->style()->wordSpacing();
2054 #endif
2055 bool nextIsSoftBreakable = false;
2057 while(len) {
2058 bool previousCharacterIsSpace = currentCharacterIsSpace;
2059 bool isSoftBreakable = nextIsSoftBreakable;
2060 nextIsSoftBreakable = false;
2061 const QChar c = str[pos];
2062 currentCharacterIsSpace = c.unicode() == ' ';
2064 if (preserveWS || !currentCharacterIsSpace)
2065 isLineEmpty = false;
2067 // Check for soft hyphens. Go ahead and ignore them.
2068 if (c.unicode() == SOFT_HYPHEN && pos > 0) {
2069 nextIsSoftBreakable = true;
2070 if (!ignoringSpaces) {
2071 // Ignore soft hyphens
2072 BidiIterator endMid(0, o, pos-1);
2073 addMidpoint(endMid);
2075 // Add the width up to but not including the hyphen.
2076 tmpW += t->width(lastSpace, pos - lastSpace, f);
2078 // For wrapping text only, include the hyphen. We need to ensure it will fit
2079 // on the line if it shows when we break.
2080 if (o->style()->autoWrap())
2081 tmpW += t->width(pos, 1, f);
2083 BidiIterator startMid(0, o, pos+1);
2084 addMidpoint(startMid);
2087 pos++;
2088 len--;
2089 lastSpace = pos; // Cheesy hack to prevent adding in widths of the run twice.
2090 continue;
2092 #ifdef APPLE_CHANGES // KDE applies wordspacing differently
2093 bool applyWordSpacing = false;
2094 #endif
2095 if (ignoringSpaces) {
2096 // We need to stop ignoring spaces, if we encounter a non-space or
2097 // a run that doesn't collapse spaces.
2098 if (!currentCharacterIsSpace || preserveWS) {
2099 // Stop ignoring spaces and begin at this
2100 // new point.
2101 ignoringSpaces = false;
2102 lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces.
2103 BidiIterator startMid ( 0, o, pos );
2104 addMidpoint(startMid);
2106 else {
2107 // Just keep ignoring these spaces.
2108 pos++;
2109 len--;
2110 continue;
2114 if ( (preserveLF && c.unicode() == '\n') || (autoWrap && (isBreakable( str, pos, strlen ) || isSoftBreakable)) ) {
2116 tmpW += t->width(lastSpace, pos - lastSpace, f);
2117 #ifdef APPLE_CHANGES
2118 applyWordSpacing = (wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace &&
2119 !t->containsOnlyWhitespace(pos+1, strlen-(pos+1)));
2120 #endif
2121 #ifdef DEBUG_LINEBREAKS
2122 kDebug(6041) << "found space at " << pos << " in string '" << QString( str, strlen ).toLatin1().constData() << "' adding " << tmpW << " new width = " << w;
2123 #endif
2124 if ( autoWrap && w + tmpW > width && w == 0 ) {
2125 int fb = nearestFloatBottom(m_height);
2126 int newLineWidth = lineWidth(fb);
2127 // See if |tmpW| will fit on the new line. As long as it does not,
2128 // keep adjusting our float bottom until we find some room.
2129 int lastFloatBottom = m_height;
2130 while (lastFloatBottom < fb && tmpW > newLineWidth) {
2131 lastFloatBottom = fb;
2132 fb = nearestFloatBottom(fb);
2133 newLineWidth = lineWidth(fb);
2136 if(!w && m_height < fb && width < newLineWidth) {
2137 m_height = fb;
2138 width = newLineWidth;
2139 #ifdef DEBUG_LINEBREAKS
2140 kDebug() << "RenderBlock::findNextLineBreak new position at " << m_height << " newWidth " << width;
2141 #endif
2145 if (autoWrap) {
2146 if (w+tmpW > width)
2147 goto end;
2148 else if ( (pos > 1 && str[pos-1].unicode() == SOFT_HYPHEN) )
2149 // Subtract the width of the soft hyphen out since we fit on a line.
2150 tmpW -= t->width(pos-1, 1, f);
2153 if( preserveLF && (str+pos)->unicode() == '\n' ) {
2154 lBreak.obj = o;
2155 lBreak.pos = pos;
2156 lBreak.endOfInline = false;
2158 #ifdef DEBUG_LINEBREAKS
2159 kDebug(6041) << "forced break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w;
2160 #endif
2161 return lBreak;
2164 if ( autoWrap ) {
2165 w += tmpW;
2166 tmpW = 0;
2167 lBreak.obj = o;
2168 lBreak.pos = pos;
2169 lBreak.endOfInline = false;
2172 lastSpace = pos;
2173 #ifdef APPLE_CHANGES
2174 if (applyWordSpacing)
2175 w += wordSpacing;
2176 #endif
2179 if (!ignoringSpaces && !preserveWS) {
2180 // If we encounter a second space, we need to go ahead and break up this run
2181 // and enter a mode where we start collapsing spaces.
2182 if (currentCharacterIsSpace && previousCharacterIsSpace) {
2183 ignoringSpaces = true;
2185 // We just entered a mode where we are ignoring
2186 // spaces. Create a midpoint to terminate the run
2187 // before the second space.
2188 addMidpoint(ignoreStart);
2189 lastSpace = pos;
2193 if (currentCharacterIsSpace && !previousCharacterIsSpace) {
2194 ignoreStart.obj = o;
2195 ignoreStart.pos = pos;
2198 if (!preserveWS && currentCharacterIsSpace && !ignoringSpaces)
2199 trailingSpaceObject = o;
2200 else if (preserveWS || !currentCharacterIsSpace)
2201 trailingSpaceObject = 0;
2203 pos++;
2204 len--;
2207 // IMPORTANT: pos is > length here!
2208 if (!ignoringSpaces)
2209 tmpW += t->width(lastSpace, pos - lastSpace, f);
2210 } else
2211 KHTMLAssert( false );
2213 InlineMinMaxIterator savedIt = lastIt;
2214 lastIt = it;
2215 o = it.next();
2217 // advance the iterator to the next non-inline-flow
2218 while (o && o->isInlineFlow() && !o->isWordBreak()) {
2219 tmpW += getBorderPaddingMargin(o, it.endOfInline);
2220 if (isLineEmpty) isLineEmpty = !tmpW;
2221 o = it.next();
2224 if (checkShouldIgnoreInitialWhitespace) {
2225 // Check if we should switch to ignoringSpaces state
2226 if (!style()->preserveWS() && it.current && it.current->isText()) {
2227 const RenderText* rt = static_cast<RenderText*>(it.current);
2228 if (rt->stringLength() > 0 && (rt->text()[0].category() == QChar::Separator_Space || rt->text()[0] == '\n')) {
2229 currentCharacterIsSpace = true;
2230 ignoringSpaces = true;
2231 BidiIterator endMid( 0, lastIt.current, 0 );
2232 addMidpoint(endMid);
2235 checkShouldIgnoreInitialWhitespace = false;
2238 bool autoWrap = lastIt.current->style()->autoWrap();
2239 bool checkForBreak = autoWrap;
2240 if (w && w + tmpW > width && lBreak.obj && !lastIt.current->style()->preserveLF() && !autoWrap)
2241 checkForBreak = true;
2242 else if (it.current && lastIt.current->isText() && it.current->isText() && !it.current->isBR()) {
2243 if (autoWrap || it.current->style()->autoWrap()) {
2244 if (currentCharacterIsSpace)
2245 checkForBreak = true;
2246 else {
2247 checkForBreak = false;
2248 RenderText* nextText = static_cast<RenderText*>(it.current);
2249 if (nextText->stringLength() != 0) {
2250 QChar c = nextText->text()[0];
2251 if (c == ' ' || c == '\t' || (c == '\n' && !it.current->style()->preserveLF())) {
2252 // If the next item on the line is text, and if we did not end with
2253 // a space, then the next text run continues our word (and so it needs to
2254 // keep adding to |tmpW|. Just update and continue.
2255 checkForBreak = true;
2259 bool canPlaceOnLine = (w + tmpW <= width+1) || !autoWrap;
2260 if (canPlaceOnLine && checkForBreak) {
2261 w += tmpW;
2262 tmpW = 0;
2263 lBreak.obj = it.current;
2264 lBreak.pos = 0;
2265 lBreak.endOfInline = it.endOfInline;
2271 if (checkForBreak && (w + tmpW > width)) {
2272 //kDebug() << " too wide w=" << w << " tmpW = " << tmpW << " width = " << width;
2273 //kDebug() << "start=" << start.obj << " current=" << o;
2274 // if we have floats, try to get below them.
2275 if (currentCharacterIsSpace && !ignoringSpaces && !lastIt.current->style()->preserveWS())
2276 trailingSpaceObject = 0;
2278 int fb = nearestFloatBottom(m_height);
2279 int newLineWidth = lineWidth(fb);
2280 // See if |tmpW| will fit on the new line. As long as it does not,
2281 // keep adjusting our float bottom until we find some room.
2282 int lastFloatBottom = m_height;
2283 while (lastFloatBottom < fb && tmpW > newLineWidth) {
2284 lastFloatBottom = fb;
2285 fb = nearestFloatBottom(fb);
2286 newLineWidth = lineWidth(fb);
2288 if( !w && m_height < fb && width < newLineWidth ) {
2289 m_height = fb;
2290 width = newLineWidth;
2291 #ifdef DEBUG_LINEBREAKS
2292 kDebug() << "RenderBlock::findNextLineBreak new position at " << m_height << " newWidth " << width;
2293 #endif
2296 // |width| may have been adjusted because we got shoved down past a float (thus
2297 // giving us more room), so we need to retest, and only jump to
2298 // the end label if we still don't fit on the line. -dwh
2299 if (w + tmpW > width) {
2300 it = lastIt;
2301 lastIt = savedIt;
2302 o = it.current;
2303 goto end;
2307 if (!lastIt.current->isFloatingOrPositioned() && lastIt.current->isReplaced() && lastIt.current->style()->autoWrap()) {
2308 // Go ahead and add in tmpW.
2309 w += tmpW;
2310 tmpW = 0;
2311 lBreak.obj = o;
2312 lBreak.pos = 0;
2313 lBreak.endOfInline = it.endOfInline;
2316 // Clear out our character space bool, since inline <pre>s don't collapse whitespace
2317 // with adjacent inline normal/nowrap spans.
2318 if (lastIt.current->style()->preserveWS())
2319 currentCharacterIsSpace = false;
2321 pos = 0;
2324 #ifdef DEBUG_LINEBREAKS
2325 kDebug( 6041 ) << "end of par, width = " << width << " linewidth = " << w + tmpW;
2326 #endif
2327 if( w + tmpW <= width || (lastIt.current && !lastIt.current->style()->autoWrap())) {
2328 lBreak.obj = 0;
2329 lBreak.pos = 0;
2330 lBreak.endOfInline = false;
2333 end:
2335 if( lBreak == start && !lBreak.obj->isBR() ) {
2336 // we just add as much as possible
2337 if ( style()->whiteSpace() == PRE ) {
2338 // FIXME: Don't really understand this case.
2339 if(pos != 0) {
2340 lBreak.obj = o;
2341 lBreak.pos = pos - 1;
2342 lBreak.endOfInline = it.endOfInline;
2343 } else {
2344 lBreak.obj = lastIt.current;
2345 lBreak.pos = lastIt.current->isText() ? lastIt.current->length() : 0;
2346 lBreak.endOfInline = lastIt.endOfInline;
2348 } else if( lBreak.obj ) {
2349 if( lastIt.current != o ) {
2350 // better to break between object boundaries than in the middle of a word
2351 lBreak.obj = o;
2352 lBreak.pos = 0;
2353 lBreak.endOfInline = it.endOfInline;
2354 } else {
2355 // Don't ever break in the middle of a word if we can help it.
2356 // There's no room at all. We just have to be on this line,
2357 // even though we'll spill out.
2358 lBreak.obj = o;
2359 lBreak.pos = pos;
2360 lBreak.endOfInline = it.endOfInline;
2365 if (hadPosStart)
2366 start = posStart;
2368 // make sure we consume at least one char/object.
2369 // and avoid returning an InlineFlow
2370 // (FIXME: turn those wordbreaks into empty text objects - they shouldn't be inline flows!)
2371 if( lBreak == start || (lBreak.obj && lBreak.obj->isInlineFlow() && !lBreak.obj->isWordBreak())) {
2372 lBreak.increment();
2375 #ifdef DEBUG_LINEBREAKS
2376 kDebug(6041) << "regular break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w;
2377 #endif
2379 // Sanity check our midpoints.
2380 checkMidpoints(lBreak);
2382 if (trailingSpaceObject) {
2383 // This object is either going to be part of the last midpoint, or it is going
2384 // to be the actual endpoint. In both cases we just decrease our pos by 1 level to
2385 // exclude the space, allowing it to - in effect - collapse into the newline.
2386 if (sNumMidpoints%2==1) {
2387 BidiIterator* midpoints = smidpoints->data();
2388 midpoints[sNumMidpoints-1].pos--;
2390 //else if (lBreak.pos > 0)
2391 // lBreak.pos--;
2392 else if (lBreak.obj == 0 && trailingSpaceObject->isText()) {
2393 // Add a new end midpoint that stops right at the very end.
2394 RenderText* text = static_cast<RenderText *>(trailingSpaceObject);
2395 unsigned pos = text->length() >=2 ? text->length() - 2 : UINT_MAX;
2396 BidiIterator endMid ( 0, trailingSpaceObject, pos );
2397 addMidpoint(endMid);
2401 // We might have made lBreak an iterator that points past the end
2402 // of the object. Do this adjustment to make it point to the start
2403 // of the next object instead to avoid confusing the rest of the
2404 // code.
2405 if (lBreak.pos > 0) {
2406 lBreak.pos--;
2407 lBreak.increment();
2410 if (lBreak.obj && lBreak.pos >= 2 && lBreak.obj->isText()) {
2411 // For soft hyphens on line breaks, we have to chop out the midpoints that made us
2412 // ignore the hyphen so that it will render at the end of the line.
2413 QChar c = static_cast<RenderText*>(lBreak.obj)->text()[lBreak.pos-1];
2414 if (c.unicode() == SOFT_HYPHEN)
2415 chopMidpointsAt(lBreak.obj, lBreak.pos-2);
2418 return lBreak;
2421 void RenderBlock::checkLinesForOverflow()
2423 for (RootInlineBox* curr = static_cast<khtml::RootInlineBox*>(firstLineBox()); curr; curr = static_cast<khtml::RootInlineBox*>(curr->nextLineBox())) {
2424 // m_overflowLeft = min(curr->leftOverflow(), m_overflowLeft);
2425 m_overflowTop = qMin(curr->topOverflow(), m_overflowTop);
2426 // m_overflowWidth = max(curr->rightOverflow(), m_overflowWidth);
2427 m_overflowHeight = qMax(curr->bottomOverflow(), m_overflowHeight);
2431 void RenderBlock::deleteEllipsisLineBoxes()
2433 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox())
2434 curr->clearTruncation();
2437 void RenderBlock::checkLinesForTextOverflow()
2439 // Determine the width of the ellipsis using the current font.
2440 QChar ellipsis = 0x2026; // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if 0x2026 not renderable
2441 static QString ellipsisStr(ellipsis);
2442 const Font& firstLineFont = style(true)->htmlFont();
2443 const Font& font = style()->htmlFont();
2444 int firstLineEllipsisWidth = firstLineFont.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
2445 int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.charWidth(&ellipsis, 1, 0, true /*fast algo*/);
2447 // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see
2448 // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and
2449 // check the left edge of the line box to see if it is less
2450 // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()"
2451 bool ltr = style()->direction() == LTR;
2452 for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) {
2453 int blockEdge = ltr ? rightOffset(curr->yPos()) : leftOffset(curr->yPos());
2454 int lineBoxEdge = ltr ? curr->xPos() + curr->width() : curr->xPos();
2455 if ((ltr && lineBoxEdge > blockEdge) || (!ltr && lineBoxEdge < blockEdge)) {
2456 // This line spills out of our box in the appropriate direction. Now we need to see if the line
2457 // can be truncated. In order for truncation to be possible, the line must have sufficient space to
2458 // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis
2459 // space.
2460 int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth;
2461 if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width))
2462 curr->placeEllipsis(ellipsisStr, ltr, blockEdge, width);
2467 // For --enable-final
2468 #undef BIDI_DEBUG
2469 #undef DEBUG_LINEBREAKS
2470 #undef DEBUG_LAYOUT
2473 // kate: space-indent on; indent-width 4; tab-width 8;