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"
40 #include "rendering/SVGRootInlineBox.h"
41 #include "rendering/SVGInlineTextBox.h"
44 //#define DEBUG_LINEBREAKS
50 // an iterator which goes through a BidiParagraph
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 );
60 const QChar
¤t() const;
61 QChar::Direction
direction() const;
70 BidiState() : context(0) {}
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
);
113 static int getBorderPaddingMargin(RenderObject
* child
, bool endOfInline
)
115 RenderStyle
* cstyle
= child
->style();
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();
129 static bool inBidiRunDetach
;
132 void BidiRun::detach(RenderArena
* renderArena
)
135 inBidiRunDetach
= true;
139 inBidiRunDetach
= false;
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.
159 static void deleteBidiRuns(RenderArena
* arena
)
164 BidiRun
* curr
= sFirstBidiRun
;
166 BidiRun
* s
= curr
->nextRun
;
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
182 BidiContext::BidiContext(unsigned char l
, QChar::Direction e
, BidiContext
*p
, bool o
)
183 : level(l
) , override(o
), dir(e
)
188 basicDir
= p
->basicDir
;
194 BidiContext::~BidiContext()
196 if(parent
) parent
->deref();
199 void BidiContext::ref() const
204 void BidiContext::deref() const
207 if(count
<= 0) delete this;
210 // ---------------------------------------------------------------------
213 inline bool operator==(const BidiContext
& c1
, const BidiContext
& c2
)
217 if (c1
.level
!= c2
.level
|| c1
.override
!= c2
.override
|| c1
.dir
!= c2
.dir
|| c1
.basicDir
!= c2
.basicDir
)
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;
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;
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;
255 *endOfInline
= false;
258 //kDebug( 6040 ) << "current = " << current;
259 if (!oldEndOfInline
&& !current
->isFloating() && !current
->isReplaced() && !current
->isPositioned()) {
260 next
= current
->firstChild();
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
) );
272 if (!skipInlines
&& !oldEndOfInline
&& current
->isInlineFlow() && endOfInline
) {
278 while (current
&& current
!= par
) {
279 next
= current
->nextSibling();
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
) {
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()))
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
);
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
);
321 inline void BidiIterator::increment(BidiState
*bidi
, bool skipInlines
)
326 if(pos
>= static_cast<RenderText
*>(obj
)->stringLength()) {
327 obj
= Bidinext( par
, obj
, bidi
, skipInlines
);
331 obj
= Bidinext( par
, obj
, bidi
, skipInlines
, &endOfInline
);
336 inline bool BidiIterator::atEnd() const
338 if(!obj
) return true;
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
);
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() )
364 return renderTxt
->text()[pos
].direction();
367 // -------------------------------------------------------------------------------------------------
369 static void addRun(BidiRun
* bidiRun
)
372 sFirstBidiRun
= sLastBidiRun
= bidiRun
;
374 sLastBidiRun
->nextRun
= bidiRun
;
375 sLastBidiRun
= bidiRun
;
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
);
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
)
393 static void reverseRuns(int start
, int end
)
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;
408 curr
= curr
->nextRun
;
411 BidiRun
* startRun
= curr
;
414 curr
= curr
->nextRun
;
416 BidiRun
* endRun
= curr
;
417 BidiRun
* afterEnd
= curr
->nextRun
;
421 BidiRun
* newNext
= afterEnd
;
424 BidiRun
* next
= curr
->nextRun
;
425 curr
->nextRun
= newNext
;
431 // Now hook up beforeStart and afterEnd to the newStart and newEnd.
433 beforeStart
->nextRun
= endRun
;
435 sFirstBidiRun
= endRun
;
437 startRun
->nextRun
= 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
) {
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
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.
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
)
484 static void addMidpoint(const BidiIterator
& midpoint
)
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
;
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;
506 midpoints
[sNumMidpoints
++] = n
;
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()))
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
))
526 // This is a new start point. Stop ignoring objects and
528 betweenMidpoints
= false;
529 start
= nextMidpoint
.pos
;
532 return appendRunsForObject(start
, end
, obj
, bidi
);
535 if (!smidpoints
|| !haveNextMidpoint
|| (obj
!= nextMidpoint
.obj
)) {
536 addRun(new (obj
->renderArena()) BidiRun(start
, end
, obj
, bidi
.context
, dir
));
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;
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
);
553 addRun(new (obj
->renderArena()) BidiRun(start
, end
, obj
, bidi
.context
, dir
));
557 static void appendRun( BidiState
&bidi
)
559 if ( emptyRun
) return;
561 kDebug(6041) << "appendRun: dir="<<(int)dir
;
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
);
569 obj
= Bidinext( bidi
.sor
.par
, obj
);
572 appendRunsForObject(start
, bidi
.eor
.pos
+1, obj
, bidi
);
574 bidi
.eor
.increment();
577 bidi
.status
.eor
= QChar::DirON
;
580 static void embed( QChar::Direction d
, BidiState
&bidi
)
583 qDebug("*** embed dir=%d emptyrun=%d", d
, emptyRun
);
585 if ( d
== QChar::DirPDF
) {
586 BidiContext
*c
= bidi
.context
->parent
;
588 if ( bidi
.eor
!= bidi
.last
) {
590 bidi
.eor
= bidi
.last
;
594 bidi
.status
.last
= bidi
.context
->dir
;
595 bidi
.context
->deref();
597 if(bidi
.context
->override
)
598 dir
= bidi
.context
->dir
;
601 bidi
.status
.lastStrong
= bidi
.context
->dir
;
604 QChar::Direction runDir
;
605 if( d
== QChar::DirRLE
|| d
== QChar::DirRLO
)
606 runDir
= QChar::DirR
;
608 runDir
= QChar::DirL
;
610 if( d
== QChar::DirLRO
|| d
== QChar::DirRLO
)
615 unsigned char level
= bidi
.context
->level
;
616 if ( runDir
== QChar::DirR
) {
617 if(level
%2) // we have an odd level
622 if(level
%2) // we have an odd level
629 if ( bidi
.eor
!= bidi
.last
) {
631 bidi
.eor
= bidi
.last
;
636 bidi
.context
= new BidiContext(level
, runDir
, bidi
.context
, override
);
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
674 InlineFlowBox
* parentBox
= createLineBoxes(obj
->parent());
675 parentBox
->addToLine(box
);
682 RootInlineBox
* RenderBlock::constructLine(const BidiIterator
&/*start*/, const BidiIterator
&end
)
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
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
711 RenderObject
* endObject
= 0;
712 bool lastLine
= !end
.obj
;
713 if (end
.obj
&& end
.pos
== 0)
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();
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()) {
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
747 int x
= leftOffset(m_height
);
748 int availableWidth
= lineWidth(m_height
);
749 switch(style()->textAlign()) {
752 if (style()->direction() == RTL
&& totWidth
> availableWidth
)
753 x
-= (totWidth
- availableWidth
);
757 if (numSpaces
!= 0 && !bidi
.current
.atEnd() && !bidi
.current
.obj
->isBR() )
762 // for right to left fall through to right aligned
763 if (bidi
.context
->basicDir
== QChar::DirL
)
767 if (style()->direction() == RTL
|| totWidth
< availableWidth
)
768 x
+= availableWidth
- totWidth
;
773 int xd
= (availableWidth
- totWidth
)/2;
780 for (r
= sFirstBidiRun
; r
; r
= r
->nextRun
) {
782 if (numSpaces
> 0 && r
->obj
->isText()) {
783 // get the number of spaces in the run
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')
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
;
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.
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());
855 kDebug(6040) << renderName() << " Line crosses to page " << xpage
;
856 kDebug(6040) << renderName() << " at pos " << lineBox
->yPos() << " height " << lineBox
->height();
860 // check page-break-inside
861 if (!style()->pageBreakInside()) {
862 if (parent()->canClear(this, PageBreakNormal
)) {
863 setNeedsPageClear(true);
868 kDebug(6040) << "Ignoring page-break-inside: avoid";
873 InlineRunBox
* box
= lineBox
->prevLineBox();
874 while (box
&& orphans
< style()->orphans()) {
876 box
= box
->prevLineBox();
880 setNeedsPageClear(true);
883 if (orphans
< style()->orphans() ) {
885 kDebug(6040) << "Orphans: " << orphans
;
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);
896 kDebug(6040) << "Ignoring violated orphans";
900 int pTop
= pageTopAfter(lineBox
->yPos());
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
);
913 kDebug(6040) << "Cleared line " << lineBox
->yPos() - oldYPos
<< "px";
915 setContainsPageBreak(true);
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
);
932 kDebug(6041) << "reordering Line from " << start
.obj
<< "/" << start
.pos
<< " to " << end
.obj
<< "/" << end
.pos
;
946 bidi
.current
= start
;
947 bidi
.last
= bidi
.current
;
950 QChar::Direction dirCurrent
;
952 //kDebug(6041) << "atEnd";
953 BidiContext
*c
= bidi
.context
;
954 if ( bidi
.current
.atEnd())
959 else if (bidi
.context
->override
) {
960 dirCurrent
= bidi
.context
->dir
;
963 dirCurrent
= bidi
.current
.direction();
966 #ifndef QT_NO_UNICODETABLES
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
;
974 // embedding and overrides (X1-X9 in the Bidi specs)
980 embed( dirCurrent
, bidi
);
985 if(dir
== QChar::DirON
)
987 switch(bidi
.status
.last
)
990 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= QChar::DirL
; break;
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
)
1011 bidi
.eor
= bidi
.current
;
1012 bidi
.status
.eor
= QChar::DirL
;
1014 if ( bidi
.status
.eor
== QChar::DirEN
|| bidi
.status
.eor
== QChar::DirAN
)
1016 dir
= bidi
.status
.eor
;
1020 bidi
.eor
= bidi
.last
;
1023 bidi
.status
.eor
= QChar::DirL
;
1026 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= QChar::DirL
;
1031 bidi
.status
.lastStrong
= QChar::DirL
;
1035 if(dir
== QChar::DirON
) dir
= QChar::DirR
;
1036 switch(bidi
.status
.last
)
1040 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= QChar::DirR
; break;
1046 bidi
.eor
= bidi
.current
;
1047 bidi
.status
.eor
= QChar::DirR
;
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
) {
1063 bidi
.eor
= bidi
.current
;
1064 bidi
.status
.eor
= QChar::DirR
;
1067 bidi
.eor
= bidi
.last
;
1070 bidi
.status
.eor
= QChar::DirR
;
1073 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= QChar::DirR
;
1078 bidi
.status
.lastStrong
= dirCurrent
;
1084 // ### if @sor, set dir to dirSor
1087 if(!(bidi
.status
.lastStrong
== QChar::DirAL
)) {
1088 // if last strong was AL change EN to AN
1089 if(dir
== QChar::DirON
) {
1092 switch(bidi
.status
.last
)
1095 if ( bidi
.status
.lastStrong
== QChar::DirR
|| bidi
.status
.lastStrong
== QChar::DirAL
) {
1098 bidi
.status
.eor
= QChar::DirEN
;
1103 bidi
.eor
= bidi
.current
;
1104 bidi
.status
.eor
= dirCurrent
;
1110 bidi
.status
.eor
= QChar::DirEN
;
1111 dir
= QChar::DirEN
; break;
1114 if(bidi
.status
.eor
== QChar::DirEN
) {
1115 bidi
.eor
= bidi
.current
; break;
1122 if(bidi
.status
.eor
== QChar::DirR
) {
1124 bidi
.eor
= bidi
.last
;
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
;
1133 // numbers on both sides, neutrals get right to left direction
1134 if(dir
!= QChar::DirL
) {
1136 bidi
.eor
= bidi
.last
;
1140 bidi
.status
.eor
= QChar::DirEN
;
1142 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= dirCurrent
;
1151 dirCurrent
= QChar::DirAN
;
1152 if(dir
== QChar::DirON
) dir
= QChar::DirAN
;
1153 switch(bidi
.status
.last
)
1157 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= QChar::DirAN
; break;
1162 dir
= QChar::DirAN
; bidi
.status
.eor
= QChar::DirAN
;
1165 if(bidi
.status
.eor
== QChar::DirAN
) {
1166 bidi
.eor
= bidi
.current
; break;
1175 if(bidi
.status
.eor
== QChar::DirR
) {
1177 bidi
.eor
= bidi
.last
;
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
;
1185 // numbers on both sides, neutrals get right to left direction
1186 if(dir
!= QChar::DirL
) {
1188 bidi
.eor
= bidi
.last
;
1192 bidi
.status
.eor
= QChar::DirAN
;
1194 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= dirCurrent
;
1205 if(bidi
.status
.last
== QChar::DirEN
) {
1206 dirCurrent
= QChar::DirEN
;
1207 bidi
.eor
= bidi
.current
; bidi
.status
.eor
= dirCurrent
;
1212 // boundary neutrals should be ignored
1217 // ### what do we do with newline and paragraph seperators that come to here?
1220 // ### implement rule L1
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.
1243 switch(bidi
.status
.last
)
1250 bidi
.status
.last
= dirCurrent
;
1253 bidi
.status
.last
= QChar::DirON
;
1261 if ( bidi
.status
.last
== QChar::DirL
) {
1266 bidi
.status
.last
= dirCurrent
;
1271 bidi
.last
= bidi
.current
;
1274 bidi
.sor
= bidi
.current
;
1275 bidi
.eor
= bidi
.current
;
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
) {
1291 kDebug(6041) << "reached end of line current=" << bidi
.current
.obj
<< "/" << bidi
.current
.pos
1292 << ", eor=" << bidi
.eor
.obj
<< "/" << bidi
.eor
.pos
<< endl
;
1294 if ( !emptyRun
&& bidi
.sor
!= bidi
.current
) {
1295 bidi
.eor
= bidi
.last
;
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
;
1306 if ( r
->level
> levelHigh
)
1307 levelHigh
= r
->level
;
1308 if ( r
->level
< levelLow
)
1309 levelLow
= r
->level
;
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
) {
1326 BidiRun
* currRun
= sFirstBidiRun
;
1327 while ( i
< count
) {
1328 while(i
< count
&& currRun
&& currRun
->level
< levelHigh
) {
1330 currRun
= currRun
->nextRun
;
1333 while(i
<= count
&& currRun
&& currRun
->level
>= levelHigh
) {
1335 currRun
= currRun
->nextRun
;
1338 reverseRuns(start
, end
);
1345 kDebug(6041) << "visual order is:";
1346 for (BidiRun
* curr
= sFirstBidiRun
; curr
; curr
= curr
->nextRun
)
1347 kDebug(6041) << " " << curr
;
1351 void RenderBlock::layoutInlineChildren(bool relayoutChildren
, int breakBeforeLine
)
1355 m_overflowHeight
= 0;
1357 invalidateVerticalPosition();
1361 kDebug( 6040 ) << renderName() << " layoutInlineChildren( " << this <<" )";
1363 #if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS )
1364 kDebug(6041) << " ------- bidi start " << this << " -------";
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();
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();
1387 // layout replaced elements
1388 RenderObject
*o
= first( this, 0, false );
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
);
1405 static_cast<RenderBox
*>(o
)->RenderBox::deleteInlineBoxes();
1407 if (fullLayout
|| o
->needsLayout())
1408 static_cast<RenderBox
*>(o
)->RenderBox::dirtyInlineBoxes(fullLayout
);
1409 o
->layoutIfNeeded();
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
;
1425 startEmbed
= new BidiContext( 1, QChar::DirR
);
1426 bidi
.status
.eor
= QChar::DirR
;
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
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.
1450 for (RootInlineBox
* line
= endLine
; line
; line
= line
->nextRootBox())
1451 line
->extractLine();
1453 // Delete the dirty area.
1455 RenderArena
* arena
= renderArena();
1456 RootInlineBox
* box
= startLine
;
1458 RootInlineBox
* next
= box
->nextRootBox();
1459 box
->deleteLine(arena
);
1464 BidiIterator end
= start
;
1465 bool endLineMatched
= false;
1469 smidpoints
= new QVector
<BidiIterator
>;
1473 sCompactFirstBidiRun
= sCompactLastBidiRun
= 0;
1474 sCompactBidiRunCount
= 0;
1476 previousLineBrokeAtBR
= true;
1479 bool pagebreakHint
= false;
1481 BidiIterator oldStart
;
1483 const bool pagedMode
= canvas()->pagedMode();
1485 while( !end
.atEnd() ) {
1487 if (endLine
&& (endLineMatched
= matchedEndLine(start
, bidi
.status
, bidi
.context
, cleanLineStart
, cleanLineBidiStatus
, cleanLineBidiContext
, endLine
, endLineYPos
)))
1490 betweenMidpoints
= false;
1492 pagebreakHint
= false;
1498 if (lineCount
== breakBeforeLine
) {
1499 m_height
= pageTopAfter(oldPos
);
1500 pagebreakHint
= true;
1503 end
= findNextLineBreak(start
, bidi
);
1504 if( start
.atEnd() ) {
1505 deleteBidiRuns(renderArena());
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
);
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
);
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
;
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
);
1555 lineBox
->setLineBreakInfo(end
.obj
, end
.pos
, bidi
.status
, bidi
.context
);
1557 m_firstLine
= false;
1563 sCompactFirstBidiRun
= sCompactLastBidiRun
= 0;
1564 sCompactBidiRunCount
= 0;
1566 startEmbed
->deref();
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())
1575 // Now apply the offset to each line if needed.
1576 int delta
= m_height
- endLineYPos
;
1578 for (RootInlineBox
* line
= endLine
; line
; line
= line
->nextRootBox())
1579 line
->adjustPosition(0, delta
);
1581 m_height
= lastRootBox()->blockHeight();
1583 // Delete all the remaining lines.
1584 InlineRunBox
* line
= endLine
;
1585 RenderArena
* arena
= renderArena();
1587 InlineRunBox
* next
= line
->nextLineBox();
1588 line
->deleteLine(arena
);
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)
1605 // find breaking line
1606 InlineRunBox
* lineBox
= firstLineBox();
1608 if (lineBox
->isInlineFlowBox()) {
1609 InlineFlowBox
* flowBox
= static_cast<InlineFlowBox
*>(lineBox
);
1610 if (flowBox
->afterPageBreak()) break;
1613 lineBox
= lineBox
->nextLineBox();
1615 InlineFlowBox
* pageBreaker
= static_cast<InlineFlowBox
*>(lineBox
);
1616 if (!pageBreaker
) goto no_break
;
1618 while (lineBox
&& widows
< style()->widows()) {
1619 if (lineBox
->hasTextChildren())
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);
1637 // Set hint and try again
1638 layoutInlineChildren(relayoutChildren
, newOrphans
+1);
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.
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();
1665 kDebug(6041) << " ------- bidi end " << this << " -------";
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;
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();
1685 RootInlineBox
* next
= curr
->nextRootBox();
1686 curr
->deleteLine(arena
);
1689 assert(!firstLineBox() && !lastLineBox());
1693 for (curr
= firstRootBox(); curr
&& !curr
->isDirty(); curr
= curr
->nextRootBox()) cnt
++;
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.
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();
1719 m_height
= last
->blockHeight();
1720 startObj
= last
->lineBreakObj();
1721 pos
= last
->lineBreakPos();
1722 bidi
.status
= last
->lineBreakBidiStatus();
1724 startObj
= first(this, &bidi
, false);
1727 start
= BidiIterator(this, startObj
, pos
);
1732 RootInlineBox
* RenderBlock::determineEndPosition(RootInlineBox
* startLine
, BidiIterator
& cleanLineStart
, BidiStatus
& cleanLineBidiStatus
, BidiContext
* cleanLineBidiContext
, int& yPos
)
1734 RootInlineBox
* last
= 0;
1738 for (RootInlineBox
* curr
= startLine
->nextRootBox(); curr
; curr
= curr
->nextRootBox()) {
1739 if (curr
->isDirty())
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();
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
);
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
) {
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.
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
);
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.
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())
1826 if (it
.obj
->isInlineFlow())
1827 return (getBorderPaddingMargin(it
.obj
, it
.endOfInline
) != 0);
1828 if (it
.obj
->style()->preserveWS() || it
.obj
->isBR())
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
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*/);
1853 BidiIterator
RenderBlock::findNextLineBreak(BidiIterator
&start
, BidiState
&bidi
)
1855 int width
= lineWidth(m_height
);
1858 #ifdef DEBUG_LINEBREAKS
1859 kDebug(6041) << "findNextLineBreak: line at " << m_height
<< " line width " << width
;
1860 kDebug(6041) << "sol: " << start
.obj
<< " " << start
.pos
;
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.
1882 // include this object then stop
1883 addMidpoint(BidiIterator(0, o
, 0));
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())
1898 if ( start
.atEnd() ){
1901 posStart
.increment();
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
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
;
1934 #ifdef DEBUG_LINEBREAKS
1935 kDebug(6041) << "new object "<< o
<<" width = " << w
<<" tmpw = " << tmpW
;
1938 if( w
+ tmpW
<= width
) {
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
1948 if (prevLineBrokeCleanly
)
1949 isLineEmpty
= false;
1950 trailingSpaceObject
= 0;
1951 previousLineBrokeAtBR
= true;
1954 // only check the clear status for non-empty lines.
1955 EClear clear
= o
->style()->clear();
1957 m_clearStatus
= (EClear
) (m_clearStatus
| clear
);
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!
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
) {
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();
2055 bool nextIsSoftBreakable
= false;
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
);
2089 lastSpace
= pos
; // Cheesy hack to prevent adding in widths of the run twice.
2092 #ifdef APPLE_CHANGES // KDE applies wordspacing differently
2093 bool applyWordSpacing
= false;
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
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
);
2107 // Just keep ignoring these spaces.
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)));
2121 #ifdef DEBUG_LINEBREAKS
2122 kDebug(6041) << "found space at " << pos
<< " in string '" << QString( str
, strlen
).toLatin1().constData() << "' adding " << tmpW
<< " new width = " << w
;
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
) {
2138 width
= newLineWidth
;
2139 #ifdef DEBUG_LINEBREAKS
2140 kDebug() << "RenderBlock::findNextLineBreak new position at " << m_height
<< " newWidth " << width
;
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' ) {
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
;
2169 lBreak
.endOfInline
= false;
2173 #ifdef APPLE_CHANGES
2174 if (applyWordSpacing
)
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
);
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;
2207 // IMPORTANT: pos is > length here!
2208 if (!ignoringSpaces
)
2209 tmpW
+= t
->width(lastSpace
, pos
- lastSpace
, f
);
2211 KHTMLAssert( false );
2213 InlineMinMaxIterator savedIt
= lastIt
;
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
;
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;
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
) {
2263 lBreak
.obj
= it
.current
;
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
) {
2290 width
= newLineWidth
;
2291 #ifdef DEBUG_LINEBREAKS
2292 kDebug() << "RenderBlock::findNextLineBreak new position at " << m_height
<< " newWidth " << width
;
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
) {
2307 if (!lastIt
.current
->isFloatingOrPositioned() && lastIt
.current
->isReplaced() && lastIt
.current
->style()->autoWrap()) {
2308 // Go ahead and add in tmpW.
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;
2324 #ifdef DEBUG_LINEBREAKS
2325 kDebug( 6041 ) << "end of par, width = " << width
<< " linewidth = " << w
+ tmpW
;
2327 if( w
+ tmpW
<= width
|| (lastIt
.current
&& !lastIt
.current
->style()->autoWrap())) {
2330 lBreak
.endOfInline
= false;
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.
2341 lBreak
.pos
= pos
- 1;
2342 lBreak
.endOfInline
= it
.endOfInline
;
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
2353 lBreak
.endOfInline
= it
.endOfInline
;
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.
2360 lBreak
.endOfInline
= it
.endOfInline
;
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())) {
2375 #ifdef DEBUG_LINEBREAKS
2376 kDebug(6041) << "regular break sol: " << start
.obj
<< " " << start
.pos
<< " end: " << lBreak
.obj
<< " " << lBreak
.pos
<< " width=" << w
;
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)
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
2405 if (lBreak
.pos
> 0) {
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);
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
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
2469 #undef DEBUG_LINEBREAKS
2473 // kate: space-indent on; indent-width 4; tab-width 8;