1 /*
2 * This file is part of the render object implementation for KHTML.
3 *
4 * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org)
5 * (C) 1999-2003 Antti Koivisto (koivisto@kde.org)
6 * (C) 2002-2003 Dirk Mueller (mueller@kde.org)
7 * (C) 2003 Apple Computer, Inc.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26 #include "render_inline.h"
27
28 #include "render_arena.h"
29 #include "render_block.h"
30 #include "rendering/render_position.h"
31
32 #include <xml/dom_docimpl.h>
33
34 using namespace khtml;
35
setStyle(RenderStyle * _style)36 void RenderInline::setStyle(RenderStyle *_style)
37 {
38 RenderFlow::setStyle(_style);
39 setInline(true);
40
41 // Ensure that all of the split inlines pick up the new style. We
42 // only do this if we're an inline, since we don't want to propagate
43 // a block's style to the other inlines.
44 // e.g., <font>foo <h4>goo</h4> moo</font>. The <font> inlines before
45 // and after the block share the same style, but the block doesn't
46 // need to pass its style on to anyone else.
47 RenderFlow *currCont = continuation();
48 while (currCont) {
49 if (currCont->isInline()) {
50 RenderFlow *nextCont = currCont->continuation();
51 currCont->setContinuation(nullptr);
52 currCont->setStyle(style());
53 currCont->setContinuation(nextCont);
54 }
55 currCont = currCont->continuation();
56 }
57
58 if (attached()) {
59 // Update replaced content
60 updateReplacedContent();
61 // Update pseudos for ::before and ::after
62 updatePseudoChildren();
63 }
64 }
65
66 // Attach handles initial setStyle that requires parent nodes
attach()67 void RenderInline::attach()
68 {
69 RenderFlow::attach();
70
71 updateReplacedContent();
72 updatePseudoChildren();
73 }
74
isInlineContinuation() const75 bool RenderInline::isInlineContinuation() const
76 {
77 return m_isContinuation;
78 }
79
addChildToFlow(RenderObject * newChild,RenderObject * beforeChild)80 void RenderInline::addChildToFlow(RenderObject *newChild, RenderObject *beforeChild)
81 {
82 // Make sure we don't append things after :after-generated content if we have it.
83 if (!beforeChild && lastChild() && lastChild()->style()->styleType() == RenderStyle::AFTER) {
84 beforeChild = lastChild();
85 }
86
87 if (!newChild->isInline() && !newChild->isFloatingOrPositioned()) {
88 // We are placing a block inside an inline. We have to perform a split of this
89 // inline into continuations. This involves creating an anonymous block box to hold
90 // |newChild|. We then make that block box a continuation of this inline. We take all of
91 // the children after |beforeChild| and put them in a clone of this object.
92
93 RenderBlock *newBox = createAnonymousBlock();
94 RenderFlow *oldContinuation = continuation();
95 setContinuation(newBox);
96
97 splitFlow(beforeChild, newBox, newChild, oldContinuation);
98 return;
99 }
100
101 RenderBox::addChild(newChild, beforeChild);
102
103 newChild->setNeedsLayoutAndMinMaxRecalc();
104 }
105
cloneInline(RenderFlow * src)106 RenderInline *RenderInline::cloneInline(RenderFlow *src)
107 {
108 RenderInline *o = new(src->renderArena()) RenderInline(src->element());
109 o->m_isContinuation = true;
110 o->setStyle(src->style());
111 return o;
112 }
113
splitInlines(RenderBlock * fromBlock,RenderBlock * toBlock,RenderBlock * middleBlock,RenderObject * beforeChild,RenderFlow * oldCont)114 void RenderInline::splitInlines(RenderBlock *fromBlock, RenderBlock *toBlock,
115 RenderBlock *middleBlock,
116 RenderObject *beforeChild, RenderFlow *oldCont)
117 {
118 // Create a clone of this inline.
119 RenderInline *clone = cloneInline(this);
120 clone->setContinuation(oldCont);
121
122 // Now take all of the children from beforeChild to the end and remove
123 // then from |this| and place them in the clone.
124 RenderObject *o = beforeChild;
125 while (o) {
126 RenderObject *tmp = o;
127 o = tmp->nextSibling();
128 clone->addChildToFlow(removeChildNode(tmp), nullptr);
129 tmp->setNeedsLayoutAndMinMaxRecalc();
130 }
131
132 // Hook |clone| up as the continuation of the middle block.
133 middleBlock->setContinuation(clone);
134
135 // We have been reparented and are now under the fromBlock. We need
136 // to walk up our inline parent chain until we hit the containing block.
137 // Once we hit the containing block we're done.
138 RenderFlow *curr = static_cast<RenderFlow *>(parent());
139 RenderFlow *currChild = this;
140 while (curr && curr != fromBlock) {
141 // Create a new clone.
142 RenderInline *cloneChild = clone;
143 clone = cloneInline(curr);
144
145 // Insert our child clone as the first child.
146 clone->addChildToFlow(cloneChild, nullptr);
147
148 // Hook the clone up as a continuation of |curr|.
149 RenderFlow *oldCont = curr->continuation();
150 curr->setContinuation(clone);
151 clone->setContinuation(oldCont);
152
153 // Now we need to take all of the children starting from the first child
154 // *after* currChild and append them all to the clone.
155 o = currChild->nextSibling();
156 while (o) {
157 RenderObject *tmp = o;
158 o = tmp->nextSibling();
159 clone->appendChildNode(curr->removeChildNode(tmp));
160 tmp->setNeedsLayoutAndMinMaxRecalc();
161 }
162
163 // Keep walking up the chain.
164 currChild = curr;
165 curr = static_cast<RenderFlow *>(curr->parent());
166 }
167
168 // Now we are at the block level. We need to put the clone into the toBlock.
169 toBlock->appendChildNode(clone);
170
171 // Now take all the children after currChild and remove them from the fromBlock
172 // and put them in the toBlock.
173 o = currChild->nextSibling();
174 while (o) {
175 RenderObject *tmp = o;
176 o = tmp->nextSibling();
177 toBlock->appendChildNode(fromBlock->removeChildNode(tmp));
178 }
179 }
180
splitFlow(RenderObject * beforeChild,RenderBlock * newBlockBox,RenderObject * newChild,RenderFlow * oldCont)181 void RenderInline::splitFlow(RenderObject *beforeChild, RenderBlock *newBlockBox,
182 RenderObject *newChild, RenderFlow *oldCont)
183 {
184 RenderBlock *pre = nullptr;
185 RenderBlock *block = containingBlock();
186 bool madeNewBeforeBlock = false;
187
188 // Delete our line boxes before we do the inline split into continuations.
189 block->deleteLineBoxTree();
190
191 if (block->isAnonymousBlock()) {
192 // We can reuse this block and make it the preBlock of the next continuation.
193 pre = block;
194 block = block->containingBlock();
195 } else {
196 // No anonymous block available for use. Make one.
197 pre = block->createAnonymousBlock();
198 madeNewBeforeBlock = true;
199 }
200
201 RenderBlock *post = block->createAnonymousBlock();
202
203 RenderObject *boxFirst = madeNewBeforeBlock ? block->firstChild() : pre->nextSibling();
204 if (madeNewBeforeBlock) {
205 block->insertChildNode(pre, boxFirst);
206 }
207 block->insertChildNode(newBlockBox, boxFirst);
208 block->insertChildNode(post, boxFirst);
209 block->setChildrenInline(false);
210
211 if (madeNewBeforeBlock) {
212 RenderObject *o = boxFirst;
213 while (o) {
214 RenderObject *no = o;
215 o = no->nextSibling();
216 pre->appendChildNode(block->removeChildNode(no));
217 no->setNeedsLayoutAndMinMaxRecalc();
218 }
219 }
220
221 splitInlines(pre, post, newBlockBox, beforeChild, oldCont);
222
223 // We already know the newBlockBox isn't going to contain inline kids, so avoid wasting
224 // time in makeChildrenNonInline by just setting this explicitly up front.
225 newBlockBox->setChildrenInline(false);
226
227 // We don't just call addChild, since it would pass things off to the
228 // continuation, so we call addChildToFlow explicitly instead. We delayed
229 // adding the newChild until now so that the |newBlockBox| would be fully
230 // connected, thus allowing newChild access to a renderArena should it need
231 // to wrap itself in additional boxes (e.g., table construction).
232 newBlockBox->addChildToFlow(newChild, nullptr);
233
234 // XXXdwh is any of this even necessary? I don't think it is.
235 pre->close();
236 pre->setPos(0, -500000);
237 pre->setNeedsLayoutAndMinMaxRecalc();
238 newBlockBox->close();
239 newBlockBox->setPos(0, -500000);
240 newBlockBox->setNeedsLayout(true);
241 post->close();
242 post->setPos(0, -500000);
243 post->setNeedsLayoutAndMinMaxRecalc();
244
245 updatePseudoChildren();
246
247 block->setNeedsLayoutAndMinMaxRecalc();
248 }
249
paint(PaintInfo & i,int _tx,int _ty)250 void RenderInline::paint(PaintInfo &i, int _tx, int _ty)
251 {
252 paintLines(i, _tx, _ty);
253 }
254
255 /**
256 * Appends the given coordinate-pair to the point-array if it is not
257 * equal to the last element.
258 * @param pointArray point-array
259 * @param pnt point to append
260 * @return \c true if \c pnt has actually been appended
261 */
appendIfNew(QVector<QPoint> & pointArray,const QPoint & pnt)262 inline static bool appendIfNew(QVector<QPoint> &pointArray, const QPoint &pnt)
263 {
264 // if (!pointArray.isEmpty()) qCDebug(KHTML_LOG) << "appifnew: " << pointArray.back() << " == " << pnt << ": " << (pointArray.back() == pnt);
265 // else qCDebug(KHTML_LOG) << "appifnew: " << pnt << " (unconditional)";
266 if (!pointArray.isEmpty() && pointArray.back() == pnt) {
267 return false;
268 }
269 pointArray.append(pnt);
270 return true;
271 }
272
273 /**
274 * Does spike-reduction on the given point-array's stack-top.
275 *
276 * Spikes are path segments of which one goes forward, and the sucessor
277 * goes backward on the predecessor's segment:
278 *
279 * 2 0 1
280 * x------x<-----x
281 * (0 is stack-top in point-array)
282 *
283 * This will be reduced to
284 * 1 0
285 * x------x
286 *
287 * Preconditions:
288 * - No other spikes exist in the whole point-array except at most
289 * one at the end
290 * - No two succeeding points are ever equal
291 * - For each two succeeding points either p1.x == p2.x or p1.y == p2.y holds
292 * true
293 * - No such spike exists where 2 is situated between 0 and 1.
294 *
295 * Postcondition:
296 * - No spikes exist in the whole point-array
297 *
298 * If no spike is found, the point-array is left unchanged.
299 * @return \c true if an actual reduction was done
300 */
reduceSpike(QVector<QPoint> & pointArray)301 inline static bool reduceSpike(QVector<QPoint> &pointArray)
302 {
303 if (pointArray.size() < 3) {
304 return false;
305 }
306 QVector<QPoint>::Iterator it = pointArray.end();
307 QPoint p0 = *--it;
308 QPoint p1 = *--it;
309 QPoint p2 = *--it;
310
311 bool elide = false;
312
313 if ((p0.x() == p1.x() && p1.x() == p2.x()
314 && ((p1.y() < p0.y() && p0.y() < p2.y())
315 || (p2.y() < p0.y() && p0.y() < p1.y())
316 || (p1.y() < p2.y() && p2.y() < p0.y())
317 || (p0.y() < p2.y() && p2.y() < p1.y())
318 || (elide = p2.y() == p0.y() && p0.y() < p1.y())
319 || (elide = p1.y() < p0.y() && p0.y() == p2.y())))
320 || (p0.y() == p1.y() && p1.y() == p2.y()
321 && ((p1.x() < p0.x() && p0.x() < p2.x())
322 || (p2.x() < p0.x() && p0.x() < p1.x())
323 || (p1.x() < p2.x() && p2.x() < p0.x())
324 || (p0.x() < p2.x() && p2.x() < p1.x())
325 || (elide = p2.x() == p0.x() && p0.x() < p1.x())
326 || (elide = p1.x() < p0.x() && p0.x() == p2.x())))) {
327 // qCDebug(KHTML_LOG) << "spikered p2" << (elide ? " (elide)" : "") << ": " << p2 << " p1: " << p1 << " p0: " << p0;
328 pointArray.pop_back(); pointArray.pop_back();
329 if (!elide) {
330 pointArray.push_back(p0);
331 }
332 return true;
333 }
334 return false;
335 }
336
337 /**
338 * Reduces segment separators.
339 *
340 * A segment separator separates a segment into two segments, thus causing
341 * two adjacent segment with the same orientation.
342 *
343 * 2 1 0
344 * x-------x---->x
345 * (0 means stack-top)
346 *
347 * Here, 1 is a segment separator. As segment separators not only make
348 * the line drawing algorithm inefficient, but also make the spike-reduction
349 * fail, they must be eliminated:
350 *
351 * 1 0
352 * x------------>x
353 *
354 * Preconditions:
355 * - No other segment separators exist in the whole point-array except
356 * at most one at the end
357 * - No two succeeding points are ever equal
358 * - For each two succeeding points either p1.x == p2.x or p1.y == p2.y holds
359 * true
360 * - No such spike exists where 2 is situated between 0 and 1.
361 *
362 * Postcondition:
363 * - No segment separators exist in the whole point-array
364 *
365 * If no segment separator is found at the end of the point-array, it is
366 * left unchanged.
367 * @return \c true if a segment separator was actually reduced.
368 */
reduceSegmentSeparator(QVector<QPoint> & pointArray)369 inline static bool reduceSegmentSeparator(QVector<QPoint> &pointArray)
370 {
371 if (pointArray.size() < 3) {
372 return false;
373 }
374 QVector<QPoint>::Iterator it = pointArray.end();
375 QPoint p0 = *--it;
376 QPoint p1 = *--it;
377 QPoint p2 = *--it;
378 // qCDebug(KHTML_LOG) << "checking p2: " << p2 << " p1: " << p1 << " p0: " << p0;
379
380 if ((p0.x() == p1.x() && p1.x() == p2.x()
381 && ((p2.y() < p1.y() && p1.y() < p0.y())
382 || (p0.y() < p1.y() && p1.y() < p2.y())))
383 || (p0.y() == p1.y() && p1.y() == p2.y()
384 && ((p2.x() < p1.x() && p1.x() < p0.x())
385 || (p0.x() < p1.x() && p1.x() < p2.x())))) {
386 // qCDebug(KHTML_LOG) << "segred p2: " << p2 << " p1: " << p1 << " p0: " << p0;
387 pointArray.pop_back(); pointArray.pop_back();
388 pointArray.push_back(p0);
389 return true;
390 }
391 return false;
392 }
393
394 /**
395 * Appends the given point to the point-array, doing necessary reductions to
396 * produce a path without spikes and segment separators.
397 */
appendPoint(QVector<QPoint> & pointArray,const QPoint & pnt)398 static void appendPoint(QVector<QPoint> &pointArray, const QPoint &pnt)
399 {
400 if (!appendIfNew(pointArray, pnt)) {
401 return;
402 }
403 // qCDebug(KHTML_LOG) << "appendPoint: appended " << pnt;
404 reduceSegmentSeparator(pointArray)
405 || reduceSpike(pointArray);
406 }
407
408 /**
409 * Traverses the horizontal inline boxes and appends the point coordinates to
410 * the given array.
411 * @param box inline box
412 * @param pointArray array collecting coordinates
413 * @param bottom \c true, collect bottom coordinates, \c false, collect top
414 * coordinates.
415 * @param limit lower limit that an y-coordinate must at least reach. Note
416 * that limit designates the highest y-coordinate for \c bottom, and
417 * the lowest for !\c bottom.
418 */
collectHorizontalBoxCoordinates(InlineBox * box,QVector<QPoint> & pointArray,bool bottom,int offset,int limit=-500000)419 static void collectHorizontalBoxCoordinates(InlineBox *box,
420 QVector<QPoint> &pointArray,
421 bool bottom, int offset, int limit = -500000)
422 {
423 // qCDebug(KHTML_LOG) << "collectHorizontalBoxCoordinates: ";
424 offset = bottom ? offset : -offset;
425 int y = box->yPos() + bottom * box->height() + offset;
426 if (limit != -500000 && (bottom ? y < limit : y > limit)) {
427 y = limit;
428 }
429 int x = box->xPos() + bottom * box->width() + offset;
430 QPoint newPnt(x, y);
431 // Add intersection point if point-array not empty.
432 if (!pointArray.isEmpty()) {
433 QPoint lastPnt = pointArray.back();
434 QPoint insPnt(newPnt.x(), lastPnt.y());
435
436 if (offset && ((bottom && lastPnt.y() > y) || (!bottom && lastPnt.y() < y))) {
437 insPnt.rx() = lastPnt.x();
438 insPnt.ry() = y;
439 }
440 // qCDebug(KHTML_LOG) << "left: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt);
441 appendPoint(pointArray, insPnt);
442 }
443 // Insert starting point of box
444 appendPoint(pointArray, newPnt);
445
446 newPnt.rx() += (bottom ? -box->width() : box->width()) - 2 * offset;
447
448 if (box->isInlineFlowBox()) {
449 InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(box);
450 for (InlineBox *b = bottom ? flowBox->lastChild() : flowBox->firstChild(); b; b = bottom ? b->prevOnLine() : b->nextOnLine()) {
451 // Don't let boxes smaller than this flow box' height influence
452 // the vertical position of the outline if they have a different
453 // x-coordinate
454 int l2;
455 if (b->xPos() != box->xPos() && b->xPos() + b->width() != box->xPos() + box->width()) {
456 l2 = y;
457 } else {
458 l2 = limit;
459 }
460 collectHorizontalBoxCoordinates(b, pointArray, bottom, qAbs(offset), l2);
461 }
462
463 // Add intersection point if flow box contained any children
464 if (flowBox->firstChild()) {
465 QPoint lastPnt = pointArray.back();
466 QPoint insPnt(lastPnt.x(), newPnt.y());
467 // qCDebug(KHTML_LOG) << "right: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt);
468 appendPoint(pointArray, insPnt);
469 }
470 }
471
472 // Insert ending point of box
473 appendPoint(pointArray, newPnt);
474
475 // qCDebug(KHTML_LOG) << "collectHorizontalBoxCoordinates: " << "ende";
476 }
477
478 /**
479 * Checks whether the given line box' extents and the following line box'
480 * extents are disjount (i. e. do not share the same x-coordinate range).
481 * @param line line box
482 * @param toBegin \c true, compare with preceding line box, \c false, with
483 * succeeding
484 * @return \c true if this and the next box are disjoint
485 */
lineBoxesDisjoint(InlineRunBox * line,int offset,bool toBegin)486 inline static bool lineBoxesDisjoint(InlineRunBox *line, int offset, bool toBegin)
487 {
488 InlineRunBox *next = toBegin ? line->prevLineBox() : line->nextLineBox();
489 return !next || next->xPos() + next->width() + 2 * offset < line->xPos()
490 || next->xPos() > line->xPos() + line->width() + 2 * offset;
491 }
492
493 /**
494 * Traverses the vertical outer borders of the given render flow's line
495 * boxes and appends the point coordinates to the given point array.
496 * @param line line box to begin traversal
497 * @param pointArray point array
498 * @param left \c true, traverse the left vertical coordinates,
499 * \c false, traverse the right vertical coordinates.
500 * @param lastline if not 0, returns the pointer to the last line box traversed
501 */
collectVerticalBoxCoordinates(InlineRunBox * line,QVector<QPoint> & pointArray,bool left,int offset,InlineRunBox ** lastline=nullptr)502 static void collectVerticalBoxCoordinates(InlineRunBox *line,
503 QVector<QPoint> &pointArray,
504 bool left, int offset, InlineRunBox **lastline = nullptr)
505 {
506 InlineRunBox *last = nullptr;
507 offset = left ? -offset : offset;
508 for (InlineRunBox *curr = line; curr && !last; curr = left ? curr->prevLineBox() : curr->nextLineBox()) {
509 InlineBox *root = curr;
510
511 bool isLast = lineBoxesDisjoint(curr, qAbs(offset), left);
512 if (isLast) {
513 last = curr;
514 }
515
516 if (root != line && !isLast)
517 while (root->parent()) {
518 root = root->parent();
519 }
520 QPoint newPnt(curr->xPos() + !left * curr->width() + offset,
521 (left ? root->topOverflow() : root->bottomOverflow()) + offset);
522 if (!pointArray.isEmpty()) {
523 QPoint lastPnt = pointArray.back();
524 if (newPnt.x() > lastPnt.x() && !left) {
525 pointArray.back().setY(qMin(lastPnt.y(), root->topOverflow() - offset));
526 } else if (newPnt.x() < lastPnt.x() && left) {
527 pointArray.back().setY(qMax(lastPnt.y(), root->bottomOverflow() + offset));
528 }
529 QPoint insPnt(newPnt.x(), pointArray.back().y());
530 // qCDebug(KHTML_LOG) << "left: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt);
531 appendPoint(pointArray, insPnt);
532 }
533 appendPoint(pointArray, newPnt);
534 }
535 if (lastline) {
536 *lastline = last;
537 }
538 }
539
540 /**
541 * Links up the end of the given point-array such that the starting point
542 * is not a segment separator.
543 *
544 * To achieve this, improper points are removed from the beginning of
545 * the point-array (by changing the array's starting iterator), and
546 * proper ones appended to the point-array's back.
547 *
548 * X---------------+ X------------------+
549 * ^ | ^ |
550 * | | ==> | |
551 * +..... ...+ +..... ...+
552 *
553 * +----->X--------+ +----------------->X
554 * | | ==> | |
555 * +..... ...+ +..... ...+
556 *
557 * ^X
558 * ||
559 * +-----++--------+ +----------------->X
560 * | | ==> | |
561 * +..... ...+ +..... ...+
562 *
563 * @param pointArray point-array
564 * @return actual begin of point array
565 */
linkEndToBegin(QVector<QPoint> & pointArray)566 static QPoint *linkEndToBegin(QVector<QPoint> &pointArray)
567 {
568 uint index = 0;
569 // ### BUG: outlines with zero width aren't treated correctly
570 // this is not the right fix
571 if (pointArray.size() < 3) {
572 return pointArray.data();
573 }
574
575 // if first and last points match, ignore the last one.
576 bool linkup = false; QPoint linkupPnt;
577 if (pointArray.front() == pointArray.back()) {
578 linkupPnt = pointArray.back();
579 pointArray.pop_back();
580 linkup = true;
581 }
582
583 const QPoint *it = pointArray.data() + index;
584 QPoint pfirst = *it;
585 QPoint pnext = *++it;
586 QPoint plast = pointArray.back();
587 // qCDebug(KHTML_LOG) << "linkcheck plast: " << plast << " pfirst: " << pfirst << " pnext: " << pnext;
588
589 if ((plast.x() == pfirst.x() && pfirst.x() == pnext.x())
590 || (plast.y() == pfirst.y() && pfirst.y() == pnext.y())) {
591
592 ++index;
593 appendPoint(pointArray, pfirst); // ### do we really need this point?
594 appendPoint(pointArray, pnext);
595 // ended up at a segment separator? move one point forward
596 if (plast == pnext) {
597 ++index;
598 appendPoint(pointArray, *++it);
599 }
600 } else if (linkup) {
601 pointArray.push_back(linkupPnt);
602 }
603 return pointArray.data() + index;
604 }
605
606 // assumes clock-wise orientation
borderSide(const QPoint & first,const QPoint & second)607 static RenderObject::BorderSide borderSide(const QPoint &first,
608 const QPoint &second)
609 {
610 if (second.x() > first.x()) {
611 return RenderObject::BSTop;
612 } else if (second.x() < first.x()) {
613 return RenderObject::BSBottom;
614 } else if (second.y() > first.y()) {
615 return RenderObject::BSRight;
616 } else { // second.y() < first.y()
617 return RenderObject::BSLeft;
618 }
619 }
620
paintOutlines(QPainter * p,int _tx,int _ty)621 void RenderInline::paintOutlines(QPainter *p, int _tx, int _ty)
622 {
623 if (style()->outlineWidth() == 0 || style()->outlineStyle() <= BHIDDEN) {
624 return;
625 }
626 int offset = style()->outlineOffset();
627
628 // We may have to draw more than one outline path as they may be
629 // disjoint.
630 for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) {
631 QVector<QPoint> path;
632
633 // collect topmost outline
634 collectHorizontalBoxCoordinates(curr, path, false, offset);
635 // collect right outline
636 collectVerticalBoxCoordinates(curr, path, false, offset, &curr);
637 // collect bottommost outline
638 collectHorizontalBoxCoordinates(curr, path, true, offset);
639 // collect left outline
640 collectVerticalBoxCoordinates(curr, path, true, offset);
641
642 if (path.size() < 3) {
643 continue;
644 }
645
646 const QPoint *begin = linkEndToBegin(path);
647
648 // initial borderside and direction values
649 QPoint pstart = *begin;
650 QPoint pprev = *(path.end() - 2);
651 RenderObject::BorderSide bs = borderSide(pprev, pstart);
652 QPoint diff = pstart - pprev;
653 int direction = diff.x() + diff.y();
654 RenderObject::BorderSide endingBS = borderSide(*begin, *(begin + 1));
655
656 // paint the outline
657 paintOutlinePath(p, _tx, _ty, begin, path.data() + path.size(),
658 bs, direction, endingBS);
659 }
660 }
661
kSwap(T & a1,T & a2)662 template<class T> inline void kSwap(T &a1, T &a2)
663 {
664 T tmp = a2;
665 a2 = a1;
666 a1 = tmp;
667 }
668
669 enum BSOrientation { BSHorizontal, BSVertical };
670
671 /**
672 * Returns the orientation of the given border side.
673 */
bsOrientation(RenderObject::BorderSide bs)674 inline BSOrientation bsOrientation(RenderObject::BorderSide bs)
675 {
676 switch (bs) {
677 case RenderObject::BSTop:
678 case RenderObject::BSBottom:
679 return BSHorizontal;
680 case RenderObject::BSLeft:
681 case RenderObject::BSRight:
682 return BSVertical;
683 }
684 return BSHorizontal; // make gcc happy (sigh)
685 }
686
687 /**
688 * Determines the new border side by evaluating the new direction as determined
689 * by the given coordinates, the old border side, and the relative direction.
690 *
691 * The relative direction specifies whether the old border side meets with the
692 * straight given by the coordinates from below/right (negative), or
693 * above/left (positive).
694 */
newBorderSide(RenderObject::BorderSide oldBS,int direction,const QPoint & last,const QPoint & cur)695 inline RenderObject::BorderSide newBorderSide(RenderObject::BorderSide oldBS, int direction, const QPoint &last, const QPoint &cur)
696 {
697 bool below = direction < 0;
698 if (last.x() == cur.x()) { // new segment is vertical
699 bool t = oldBS == RenderObject::BSTop;
700 bool b = oldBS == RenderObject::BSBottom;
701 if ((t || b) && last.y() != cur.y())
702 return (cur.y() < last.y()) ^ ((t && below) || (b && !below))
703 ? RenderObject::BSLeft : RenderObject::BSRight;
704 } else { /*if (last.y() == cur.y())*/ // new segment is horizontal
705 bool l = oldBS == RenderObject::BSLeft;
706 bool r = oldBS == RenderObject::BSRight;
707 if ((l || r) && last.x() != cur.x())
708 return (cur.x() < last.x()) ^ ((l && below) || (r && !below))
709 ? RenderObject::BSTop : RenderObject::BSBottom;
710 }
711 return oldBS; // same direction
712 }
713
714 /**
715 * Draws an outline segment between the given two points.
716 * @param o render object
717 * @param p painter
718 * @param tx absolute x-coordinate of containing block
719 * @param ty absolute y-coordinate of containing block
720 * @param p1 starting point
721 * @param p2 end point
722 * @param prevBS border side of previous segment
723 * @param curBS border side of this segment
724 * @param nextBS border side of next segment
725 */
paintOutlineSegment(RenderObject * o,QPainter * p,int tx,int ty,const QPoint & p1,const QPoint & p2,RenderObject::BorderSide prevBS,RenderObject::BorderSide curBS,RenderObject::BorderSide nextBS)726 static void paintOutlineSegment(RenderObject *o, QPainter *p, int tx, int ty,
727 const QPoint &p1, const QPoint &p2,
728 RenderObject::BorderSide prevBS,
729 RenderObject::BorderSide curBS,
730 RenderObject::BorderSide nextBS)
731 {
732 int ow = o->style()->outlineWidth();
733 EBorderStyle os = o->style()->outlineStyle();
734 QColor oc = o->style()->outlineColor();
735
736 int x1 = tx + p1.x();
737 int y1 = ty + p1.y();
738 int x2 = tx + p2.x();
739 int y2 = ty + p2.y();
740 if (x1 > x2) {
741 kSwap(x1, x2);
742 if (bsOrientation(curBS) == BSHorizontal) {
743 kSwap(prevBS, nextBS);
744 }
745 }
746 if (y1 > y2) {
747 kSwap(y1, y2);
748 if (bsOrientation(curBS) == BSVertical) {
749 kSwap(prevBS, nextBS);
750 }
751 }
752
753 // qCDebug(KHTML_LOG) << "segment(" << x1 << "," << y1 << ") - (" << x2 << "," << y2 << ")";
754 /* p->setPen(Qt::gray);
755 p->drawLine(x1,y1,x2,y2);*/
756 switch (curBS) {
757 case RenderObject::BSLeft:
758 case RenderObject::BSRight:
759 /* p->setPen(QColor("#ffe4dd"));
760 p->drawLine(
761 x1 - (curBS == RenderObject::BSLeft ? ow : 0),
762 y1 - (prevBS == RenderObject::BSTop ? ow : 0),
763 x2 + (curBS == RenderObject::BSRight ? ow : 0),
764 y2 + (nextBS == RenderObject::BSBottom ? ow : 0)
765 );*/
766 o->drawBorder(p,
767 x1 - (curBS == RenderObject::BSLeft ? ow : 0),
768 y1 - (prevBS == RenderObject::BSTop ? ow : 0),
769 x2 + (curBS == RenderObject::BSRight ? ow : 0),
770 y2 + (nextBS == RenderObject::BSBottom ? ow : 0),
771 curBS, oc, o->style()->color(), os,
772 prevBS == RenderObject::BSTop ? ow
773 : prevBS == RenderObject::BSBottom ? -ow : 0,
774 nextBS == RenderObject::BSTop ? -ow
775 : nextBS == RenderObject::BSBottom ? ow : 0,
776 true);
777 break;
778 case RenderObject::BSBottom:
779 case RenderObject::BSTop:
780 // qCDebug(KHTML_LOG) << "BSTop/BSBottom: prevBS " << prevBS << " curBS " << curBS << " nextBS " << nextBS;
781 o->drawBorder(p,
782 x1 - (prevBS == RenderObject::BSLeft ? ow : 0),
783 y1 - (curBS == RenderObject::BSTop ? ow : 0),
784 x2 + (nextBS == RenderObject::BSRight ? ow : 0),
785 y2 + (curBS == RenderObject::BSBottom ? ow : 0),
786 curBS, oc, o->style()->color(), os,
787 prevBS == RenderObject::BSLeft ? ow
788 : prevBS == RenderObject::BSRight ? -ow : 0,
789 nextBS == RenderObject::BSLeft ? -ow
790 : nextBS == RenderObject::BSRight ? ow : 0,
791 true);
792 break;
793 }
794 }
795
paintOutlinePath(QPainter * p,int tx,int ty,const QPoint * begin,const QPoint * end,BorderSide bs,int direction,BorderSide endingBS)796 void RenderInline::paintOutlinePath(QPainter *p, int tx, int ty, const QPoint *begin, const QPoint *end, BorderSide bs, int direction, BorderSide endingBS)
797 {
798 int ow = style()->outlineWidth();
799 if (ow == 0 || m_isContinuation) { // Continuations get painted by the original inline.
800 return;
801 }
802
803 QPoint last = *begin;
804 BorderSide lastBS = bs;
805 Q_ASSERT(begin != end);
806 ++begin;
807
808 // qCDebug(KHTML_LOG) << "last: " << last;
809
810 bs = newBorderSide(bs, direction, last, *begin);
811 // qCDebug(KHTML_LOG) << "newBorderSide: " << lastBS << " " << direction << "d " << last << " - " << *begin << " => " << bs;
812
813 for (const QPoint *it = begin; it != end; ++it) {
814 QPoint cur = *it;
815 // qCDebug(KHTML_LOG) << "cur: " << cur;
816 BorderSide nextBS;
817 if (it + 1 != end) {
818 QPoint diff = cur - last;
819 direction = diff.x() + diff.y();
820 nextBS = newBorderSide(bs, direction, cur, *(it + 1));
821 // qCDebug(KHTML_LOG) << "newBorderSide*: " << bs << " " << direction << "d " << cur << " - " << *(it + 1) << " => " << nextBS;
822 } else {
823 nextBS = endingBS;
824 }
825
826 Q_ASSERT(bsOrientation(bs) != bsOrientation(nextBS));
827 paintOutlineSegment(this, p, tx, ty, last, cur,
828 lastBS, bs, nextBS);
829 lastBS = bs;
830 last = cur;
831 bs = nextBS;
832 }
833
834 }
835
calcMinMaxWidth()836 void RenderInline::calcMinMaxWidth()
837 {
838 KHTMLAssert(!minMaxKnown());
839
840 #ifdef DEBUG_LAYOUT
841 // qCDebug(KHTML_LOG) << renderName() << "(RenderInline)::calcMinMaxWidth() this=" << this;
842 #endif
843
844 // Irrelevant, since some enclosing block will actually measure us and our children.
845 m_minWidth = 0;
846 m_maxWidth = 0;
847
848 setMinMaxKnown();
849 }
850
width() const851 short RenderInline::width() const
852 {
853 // Return the width of the minimal left side and the maximal right side.
854 short leftSide = 0;
855 short rightSide = 0;
856 for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) {
857 if (curr == firstLineBox() || curr->xPos() < leftSide) {
858 leftSide = curr->xPos();
859 }
860 if (curr == firstLineBox() || curr->xPos() + curr->width() > rightSide) {
861 rightSide = curr->xPos() + curr->width();
862 }
863 }
864
865 return rightSide - leftSide;
866 }
867
height() const868 int RenderInline::height() const
869 {
870 int h = 0;
871 if (firstLineBox()) {
872 h = lastLineBox()->yPos() + lastLineBox()->height() - firstLineBox()->yPos();
873 }
874 return h;
875 }
876
offsetLeft() const877 int RenderInline::offsetLeft() const
878 {
879 int x = RenderFlow::offsetLeft();
880 if (firstLineBox()) {
881 x += firstLineBox()->xPos();
882 }
883 return x;
884 }
885
offsetTop() const886 int RenderInline::offsetTop() const
887 {
888 int y = RenderFlow::offsetTop();
889 if (firstLineBox()) {
890 y += firstLineBox()->yPos();
891 }
892 return y;
893 }
894
renderName() const895 const char *RenderInline::renderName() const
896 {
897 if (isRelPositioned()) {
898 return "RenderInline (relative positioned)";
899 }
900 if (isAnonymous()) {
901 return "RenderInline (anonymous)";
902 }
903 return "RenderInline";
904 }
905
nodeAtPoint(NodeInfo & info,int _x,int _y,int _tx,int _ty,HitTestAction hitTestAction,bool inside)906 bool RenderInline::nodeAtPoint(NodeInfo &info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside)
907 {
908
909 // Check our line boxes if we're still not inside.
910 if (!inside) {
911 // See if we're inside one of our line boxes.
912 inside = hitTestLines(info, _x, _y, _tx, _ty, hitTestAction);
913 }
914
915 if (inside && element() && style()->visibility() != HIDDEN) {
916 if (info.innerNode() && info.innerNode()->renderer() &&
917 !info.innerNode()->renderer()->isInline()) {
918 // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node.
919 info.setInnerNode(element());
920
921 // Clear everything else.
922 info.setInnerNonSharedNode(nullptr);
923 info.setURLElement(nullptr);
924 }
925
926 if (!info.innerNode()) {
927 info.setInnerNode(element());
928 }
929
930 if (!info.innerNonSharedNode()) {
931 info.setInnerNonSharedNode(element());
932 }
933 }
934
935 return inside;
936 }
937
positionForCoordinates(int x,int y)938 RenderPosition RenderInline::positionForCoordinates(int x, int y)
939 {
940 for (RenderObject *c = continuation(); c; c = c->continuation()) {
941 if (c->isInline() || c->firstChild()) {
942 return c->positionForCoordinates(x, y);
943 }
944 }
945
946 return RenderFlow::positionForCoordinates(x, y);
947 }
948
caretPos(int offset,int flags,int & _x,int & _y,int & width,int & height) const949 void RenderInline::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) const
950 {
951 _x = -1;
952
953 RenderBlock *cb = containingBlock();
954 bool rtl = cb->style()->direction() == RTL;
955 bool outsideEnd = flags & CFOutsideEnd;
956 // I need to explain that: outsideEnd contains a meaningful value if
957 // and only if flags & CFOutside is set. If it is not, then randomly
958 // either the first or the last line box is returned.
959 // This doesn't matter because the only case this can happen is on an
960 // empty inline element, whose first and last line boxes are actually
961 // the same.
962 InlineFlowBox *line = !outsideEnd ^ rtl ? firstLineBox() : lastLineBox();
963
964 if (!line) { // umpf, handle "gracefully"
965 RenderFlow::caretPos(offset, flags, _x, _y, width, height);
966 return;
967 }
968
969 _x = line->xPos();
970 width = 1; // ### regard CFOverride
971
972 // Place caret outside the border
973 if (flags & CFOutside) {
974 RenderStyle *s = element() && element()->parent()
975 && element()->parent()->renderer()
976 ? element()->parent()->renderer()->style()
977 : style();
978 const QFontMetrics &fm = s->fontMetrics();
979 _y = line->yPos() + line->baseline() - fm.ascent();
980 height = fm.height();
981
982 if (!outsideEnd ^ rtl) {
983 _x -= line->marginBorderPaddingLeft();
984 } else {
985 _x += line->width() + line->marginBorderPaddingRight();
986 }
987
988 } else {
989 const QFontMetrics &fm = style()->fontMetrics();
990 _y = line->yPos() + line->baseline() - fm.ascent();
991 height = fm.height();
992 }
993
994 int absx, absy;
995 if (cb && cb->absolutePosition(absx, absy)) {
996 //qCDebug(KHTML_LOG) << "absx=" << absx << " absy=" << absy;
997 _x += absx;
998 _y += absy;
999 } else {
1000 // we don't know our absolute position, and there is no point returning
1001 // just a relative one
1002 _x = _y = -1;
1003 }
1004 }
1005
minXPos(const RenderInline * o)1006 inline int minXPos(const RenderInline *o)
1007 {
1008 int retval = 6666666;
1009 if (!o->firstLineBox()) {
1010 return 0;
1011 }
1012 for (InlineRunBox *curr = o->firstLineBox(); curr; curr = curr->nextLineBox()) {
1013 retval = qMin(retval, int(curr->m_x));
1014 }
1015 return retval;
1016 }
1017
inlineXPos() const1018 int RenderInline::inlineXPos() const
1019 {
1020 return minXPos(this);
1021 }
1022
inlineYPos() const1023 int RenderInline::inlineYPos() const
1024 {
1025 return firstLineBox() ? firstLineBox()->yPos() : 0;
1026 }
1027
1028