KHtml

render_flow.cpp
1 
25 // -------------------------------------------------------------------------
26 
27 #include "render_flow.h"
28 
29 #include "khtml_debug.h"
30 #include <assert.h>
31 #include <QPainter>
32 
33 #include "render_text.h"
34 #include "render_table.h"
35 #include "render_canvas.h"
36 #include "render_inline.h"
37 #include "render_block.h"
38 #include "render_arena.h"
39 #include "render_line.h"
40 #include <xml/dom_nodeimpl.h>
41 #include <xml/dom_docimpl.h>
42 #include <html/html_formimpl.h>
43 
44 #include <khtmlview.h>
45 
46 using namespace DOM;
47 using namespace khtml;
48 
49 RenderFlow *RenderFlow::createFlow(DOM::NodeImpl *node, RenderStyle *style, RenderArena *arena)
50 {
51  RenderFlow *result;
52  if (style->display() == INLINE) {
53  result = new(arena) RenderInline(node);
54  } else {
55  result = new(arena) RenderBlock(node);
56  }
57  result->setStyle(style);
58  return result;
59 }
60 
61 RenderFlow *RenderFlow::continuationBefore(const RenderObject *beforeChild)
62 {
63  if (beforeChild && beforeChild->parent() == this) {
64  return this;
65  }
66 
67  RenderFlow *curr = continuation();
68  RenderFlow *nextToLast = this;
69  RenderFlow *last = this;
70  while (curr) {
71  if (beforeChild && beforeChild->parent() == curr) {
72  if (curr->firstChild() == beforeChild) {
73  return last;
74  }
75  return curr;
76  }
77 
78  nextToLast = last;
79  last = curr;
80  curr = curr->continuation();
81  }
82 
83  if (!beforeChild && !last->firstChild()) {
84  return nextToLast;
85  }
86  return last;
87 }
88 
89 void RenderFlow::addChildWithContinuation(RenderObject *newChild, RenderObject *beforeChild)
90 {
91  RenderFlow *flow = continuationBefore(beforeChild);
92 
93  RenderObject *bc = beforeChild;
94  while (bc && bc->parent() != flow && !bc->parent()->isAnonymousBlock()) {
95  // skip implicit containers around beforeChild
96  bc = bc->parent();
97  }
98 
99  RenderFlow *beforeChildParent = bc ? static_cast<RenderFlow *>(bc->parent()) :
100  (flow->continuation() ? flow->continuation() : flow);
101 
102  if (newChild->isFloatingOrPositioned()) {
103  return beforeChildParent->addChildToFlow(newChild, beforeChild);
104  }
105 
106  // A continuation always consists of two potential candidates: an inline or an anonymous
107  // block box holding block children.
108  bool childInline = newChild->isInline();
109  bool bcpInline = beforeChildParent->isInline();
110  bool flowInline = flow->isInline();
111 
112  if (flow == beforeChildParent) {
113  return flow->addChildToFlow(newChild, beforeChild);
114  } else {
115  // The goal here is to match up if we can, so that we can coalesce and create the
116  // minimal # of continuations needed for the inline.
117  if (childInline == bcpInline) {
118  return beforeChildParent->addChildToFlow(newChild, beforeChild);
119  } else if (flowInline == childInline) {
120  return flow->addChildToFlow(newChild, nullptr); // Just treat like an append.
121  } else {
122  return beforeChildParent->addChildToFlow(newChild, beforeChild);
123  }
124  }
125 }
126 
127 void RenderFlow::addChild(RenderObject *newChild, RenderObject *beforeChild)
128 {
129 #ifdef DEBUG_LAYOUT
130  // qCDebug(KHTML_LOG) << renderName() << "(RenderFlow)::addChild( " << newChild->renderName() <<
131  // ", " << (beforeChild ? beforeChild->renderName() : "0") << " )";
132  // qCDebug(KHTML_LOG) << "current height = " << m_height;
133 #endif
134 
135  if (continuation()) {
136  return addChildWithContinuation(newChild, beforeChild);
137  }
138  return addChildToFlow(newChild, beforeChild);
139 }
140 
141 void RenderFlow::extractLineBox(InlineFlowBox *box)
142 {
143  m_lastLineBox = box->prevFlowBox();
144  if (box == m_firstLineBox) {
145  m_firstLineBox = nullptr;
146  }
147  if (box->prevLineBox()) {
148  box->prevLineBox()->setNextLineBox(nullptr);
149  }
150  box->setPreviousLineBox(nullptr);
151  for (InlineRunBox *curr = box; curr; curr = curr->nextLineBox()) {
152  curr->setExtracted();
153  }
154 }
155 
156 void RenderFlow::attachLineBox(InlineFlowBox *box)
157 {
158  if (m_lastLineBox) {
159  m_lastLineBox->setNextLineBox(box);
160  box->setPreviousLineBox(m_lastLineBox);
161  } else {
162  m_firstLineBox = box;
163  }
164  InlineFlowBox *last = box;
165  for (InlineFlowBox *curr = box; curr; curr = curr->nextFlowBox()) {
166  curr->setExtracted(false);
167  last = curr;
168  }
169  m_lastLineBox = last;
170 }
171 
172 void RenderFlow::removeInlineBox(InlineBox *_box)
173 {
174  if (_box->isInlineFlowBox()) {
175  InlineFlowBox *box = static_cast<InlineFlowBox *>(_box);
176  if (box == m_firstLineBox) {
177  m_firstLineBox = box->nextFlowBox();
178  }
179  if (box == m_lastLineBox) {
180  m_lastLineBox = box->prevFlowBox();
181  }
182  if (box->nextLineBox()) {
183  box->nextLineBox()->setPreviousLineBox(box->prevLineBox());
184  }
185  if (box->prevLineBox()) {
186  box->prevLineBox()->setNextLineBox(box->nextLineBox());
187  }
188  }
189  RenderBox::removeInlineBox(_box);
190 }
191 
192 void RenderFlow::deleteInlineBoxes(RenderArena *arena)
193 {
194  if (m_firstLineBox) {
195  if (!arena) {
196  arena = renderArena();
197  }
198  InlineRunBox *curr = m_firstLineBox, *next = nullptr;
199  while (curr) {
200  next = curr->nextLineBox();
201  if (!curr->isPlaceHolderBox()) {
202  curr->detach(arena, true /*noRemove*/);
203  }
204  curr = next;
205  }
206  m_firstLineBox = nullptr;
207  m_lastLineBox = nullptr;
208  }
209 }
210 
211 void RenderFlow::dirtyInlineBoxes(bool fullLayout, bool isRootLineBox)
212 {
213  if (!isRootLineBox && (isReplaced() || isPositioned())) {
214  return RenderBox::dirtyInlineBoxes(fullLayout, isRootLineBox);
215  }
216 
217  if (fullLayout) {
218  deleteInlineBoxes();
219  } else {
220  for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) {
221  curr->dirtyInlineBoxes();
222  }
223  }
224 }
225 
226 void RenderFlow::deleteLastLineBox(RenderArena *arena)
227 {
228  if (m_lastLineBox) {
229  if (!arena) {
230  arena = renderArena();
231  }
232  InlineRunBox *curr = m_lastLineBox, *prev = m_lastLineBox;
233  if (m_firstLineBox == m_lastLineBox) {
234  m_firstLineBox = m_lastLineBox = nullptr;
235  } else {
236  prev = curr->prevLineBox();
237  while (!prev->isInlineFlowBox()) {
238  prev = prev->prevLineBox();
239  prev->detach(arena);
240  }
241  m_lastLineBox = static_cast<InlineFlowBox *>(prev);
242  prev->setNextLineBox(nullptr);
243  }
244  if (curr->parent()) {
245  curr->parent()->removeFromLine(curr);
246  }
247  curr->detach(arena);
248  }
249 }
250 
251 InlineBox *RenderFlow::createInlineBox(bool makePlaceHolderBox, bool isRootLineBox)
252 {
253  if (!isRootLineBox &&
254  (isReplaced() || makePlaceHolderBox)) { // Inline tables and inline blocks
255  return RenderBox::createInlineBox(false, false); // (or positioned element placeholders).
256  }
257 
258  InlineFlowBox *flowBox = nullptr;
259  if (isInlineFlow()) {
260  flowBox = new(renderArena()) InlineFlowBox(this);
261  } else {
262  flowBox = new(renderArena()) RootInlineBox(this);
263  }
264 
265  if (!m_firstLineBox) {
266  m_firstLineBox = m_lastLineBox = flowBox;
267  } else {
268  m_lastLineBox->setNextLineBox(flowBox);
269  flowBox->setPreviousLineBox(m_lastLineBox);
270  m_lastLineBox = flowBox;
271  }
272 
273  return flowBox;
274 }
275 
276 void RenderFlow::dirtyLinesFromChangedChild(RenderObject *child)
277 {
278  if (!parent() || (selfNeedsLayout() && !isInlineFlow()) || isTable()) {
279  return;
280  }
281 
282  // If we have no first line box, then just bail early.
283  if (!firstLineBox()) {
284  // For an empty inline, propagate the check up to our parent, unless the parent
285  // is already dirty.
286  if (isInline() && !parent()->selfNeedsLayout() && parent()->isInlineFlow()) {
287  static_cast<RenderFlow *>(parent())->dirtyLinesFromChangedChild(this);
288  }
289  return;
290  }
291 
292  // Try to figure out which line box we belong in. First try to find a previous
293  // line box by examining our siblings. If we didn't find a line box, then use our
294  // parent's first line box.
295  RootInlineBox *box = nullptr;
296  RenderObject *curr = nullptr;
297  for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) {
298  if (curr->isFloatingOrPositioned()) {
299  continue;
300  }
301 
302  if (curr->isReplaced() && curr->isBox()) {
303  InlineBox *placeHolderBox = static_cast<RenderBox *>(curr)->placeHolderBox();
304  if (placeHolderBox) {
305  box = placeHolderBox->root();
306  }
307  } else if (curr->isText()) {
308  InlineTextBox *textBox = static_cast<RenderText *>(curr)->lastTextBox();
309  if (textBox) {
310  box = textBox->root();
311  }
312  } else if (curr->isInlineFlow()) {
313  InlineRunBox *runBox = static_cast<RenderFlow *>(curr)->lastLineBox();
314  if (runBox) {
315  box = runBox->root();
316  }
317  }
318 
319  if (box) {
320  break;
321  }
322  }
323  if (!box) {
324  box = firstLineBox()->root();
325  }
326 
327  // If we found a line box, then dirty it.
328  if (box) {
329  RootInlineBox *adjacentBox;
330  box->markDirty();
331 
332  // dirty the adjacent lines that might be affected
333  // NOTE: we dirty the previous line because RootInlineBox objects cache
334  // the address of the first object on the next line after a BR, which we may be
335  // invalidating here. For more info, see how RenderBlock::layoutInlineChildren
336  // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak,
337  // despite the name, actually returns the first RenderObject after the BR.
338 
339  adjacentBox = box->prevRootBox();
340  if (adjacentBox) {
341  adjacentBox->markDirty();
342  }
343  if (child->isBR() || (curr && curr->isBR())) {
344  adjacentBox = box->nextRootBox();
345  if (adjacentBox) {
346  adjacentBox->markDirty();
347  }
348  }
349  }
350 }
351 
352 QList< QRectF > RenderFlow::getClientRects()
353 {
354  if (isRenderInline() && isInlineFlow()) {
356 
357  InlineFlowBox *child = firstLineBox();
358  if (child) {
359  int x = 0, y = 0;
360  absolutePosition(x,y);
361  do {
362  QRectF rect(x + child->xPos(), y + child->yPos(), child->width(), child->height());
363  list.append(clientRectToViewport(rect));
364  child = child->nextFlowBox();
365  } while (child);
366  }
367 
368  // In case our flow is splitted by blocks
369  for (RenderObject *cont = continuation(); cont; cont = cont->continuation()) {
370  list.append(cont->getClientRects());
371  }
372 
373  // Empty Flow, return the Flow itself
374  if (list.isEmpty()) {
375  return RenderObject::getClientRects();
376  }
377 
378  return list;
379  } else {
380  return RenderObject::getClientRects();
381  }
382 }
383 
384 void RenderFlow::detach()
385 {
386  if (continuation()) {
387  continuation()->detach();
388  }
389  m_continuation = nullptr;
390 
391  // Make sure to destroy anonymous children first while they are still connected to the rest of the tree, so that they will
392  // properly dirty line boxes that they are removed from. Effects that do :before/:after only on hover could crash otherwise.
393  detachRemainingChildren();
394 
395  if (!documentBeingDestroyed()) {
396  if (m_firstLineBox) {
397  // We can't wait for RenderContainer::destroy to clear the selection,
398  // because by then we will have nuked the line boxes.
399  if (isSelectionBorder()) {
400  canvas()->clearSelection();
401  }
402 
403  // If line boxes are contained inside a root, that means we're an inline.
404  // In that case, we need to remove all the line boxes so that the parent
405  // lines aren't pointing to deleted children. If the first line box does
406  // not have a parent that means they are either already disconnected or
407  // root lines that can just be destroyed without disconnecting.
408  if (m_firstLineBox->parent()) {
409  for (InlineRunBox *box = m_firstLineBox; box; box = box->nextLineBox()) {
410  box->remove();
411  }
412  }
413 
414  // If we are an anonymous block, then our line boxes might have children
415  // that will outlast this block. In the non-anonymous block case those
416  // children will be destroyed by the time we return from this function.
417  if (isAnonymousBlock()) {
418  for (InlineFlowBox *box = m_firstLineBox; box; box = box->nextFlowBox()) {
419  while (InlineBox *childBox = box->firstChild()) {
420  childBox->remove();
421  }
422  }
423  }
424  } else if (isInline() && parent())
425  // empty inlines propagate linebox dirtying to the parent
426  {
427  parent()->dirtyLinesFromChangedChild(this);
428  }
429  }
430 
431  deleteInlineBoxes();
432 
433  RenderBox::detach();
434 }
435 
436 void RenderFlow::paintLines(PaintInfo &i, int _tx, int _ty)
437 {
438  // Only paint during the foreground/selection phases.
439  if (i.phase != PaintActionForeground && i.phase != PaintActionSelection && i.phase != PaintActionOutline) {
440  return;
441  }
442 
443  if (!firstLineBox()) {
444  return;
445  }
446 
447  // We can check the first box and last box and avoid painting if we don't
448  // intersect. This is a quick short-circuit that we can take to avoid walking any lines.
449  // FIXME: This check is flawed in two extremely obscure ways.
450  // (1) If some line in the middle has a huge overflow, it might actually extend below the last line.
451  // (2) The overflow from an inline block on a line is not reported to the line.
452  int maxOutlineSize = maximalOutlineSize(i.phase);
453  int yPos = firstLineBox()->root()->topOverflow() - maxOutlineSize;
454  int h = maxOutlineSize + lastLineBox()->root()->bottomOverflow() - yPos;
455  yPos += _ty;
456  if ((yPos >= i.r.y() + i.r.height()) || (yPos + h <= i.r.y())) {
457  return;
458  }
459  for (InlineFlowBox *curr = firstLineBox(); curr; curr = curr->nextFlowBox()) {
460  yPos = curr->root()->topOverflow() - maxOutlineSize;
461  h = curr->root()->bottomOverflow() + maxOutlineSize - yPos;
462  yPos += _ty;
463  if ((yPos < i.r.y() + i.r.height()) && (yPos + h > i.r.y())) {
464  curr->paint(i, _tx, _ty);
465  }
466  }
467 
468  if (i.phase == PaintActionOutline && i.outlineObjects) {
469  foreach (RenderFlow *oo, *i.outlineObjects)
470  if (oo->isRenderInline()) {
471  static_cast<RenderInline *>(oo)->paintOutlines(i.p, _tx, _ty);
472  }
473  i.outlineObjects->clear();
474  }
475 }
476 
477 bool RenderFlow::hitTestLines(NodeInfo &i, int x, int y, int tx, int ty, HitTestAction hitTestAction)
478 {
479  (void) hitTestAction;
480  /*
481  if (hitTestAction != HitTestForeground) // ### port hitTest
482  return false;
483  */
484 
485  if (!firstLineBox()) {
486  return false;
487  }
488 
489  // We can check the first box and last box and avoid hit testing if we don't
490  // contain the point. This is a quick short-circuit that we can take to avoid walking any lines.
491  // FIXME: This check is flawed in two extremely obscure ways.
492  // (1) If some line in the middle has a huge overflow, it might actually extend below the last line.
493  // (2) The overflow from an inline block on a line is not reported to the line.
494  if ((y >= ty + lastLineBox()->root()->bottomOverflow()) || (y < ty + firstLineBox()->root()->topOverflow())) {
495  return false;
496  }
497 
498  // See if our root lines contain the point. If so, then we hit test
499  // them further. Note that boxes can easily overlap, so we can't make any assumptions
500  // based off positions of our first line box or our last line box.
501  for (InlineFlowBox *curr = lastLineBox(); curr; curr = curr->prevFlowBox()) {
502  if (y >= ty + curr->root()->topOverflow() && y < ty + curr->root()->bottomOverflow()) {
503  bool inside = curr->nodeAtPoint(i, x, y, tx, ty);
504  if (inside) {
505  setInnerNode(i);
506  return true;
507  }
508  }
509  }
510 
511  return false;
512 }
513 
514 void RenderFlow::repaint(Priority prior)
515 {
516  if (isInlineFlow()) {
517  // Find our leftmost position.
518  int left = 0;
519  // root inline box not reliably availabe during relayout
520  int top = firstLineBox() ? (
521  needsLayout() ? firstLineBox()->xPos() : firstLineBox()->root()->topOverflow()
522  ) : 0;
523  for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox())
524  if (curr == firstLineBox() || curr->xPos() < left) {
525  left = curr->xPos();
526  }
527 
528  // Now invalidate a rectangle.
529  int ow = style() ? style()->outlineSize() : 0;
530 
531  // We need to add in the relative position offsets of any inlines (including us) up to our
532  // containing block.
533  RenderBlock *cb = containingBlock();
534  for (RenderObject *inlineFlow = this; inlineFlow && inlineFlow->isInlineFlow() && inlineFlow != cb;
535  inlineFlow = inlineFlow->parent()) {
536  if (inlineFlow->style() && inlineFlow->style()->position() == PRELATIVE && inlineFlow->layer()) {
537  KHTMLAssert(inlineFlow->isBox());
538  static_cast<RenderBox *>(inlineFlow)->relativePositionOffset(left, top);
539  }
540  }
541 
542  RootInlineBox *lastRoot = lastLineBox() && !needsLayout() ? lastLineBox()->root() : nullptr;
543  containingBlock()->repaintRectangle(-ow + left, -ow + top,
544  width() + ow * 2,
545  (lastRoot ? lastRoot->bottomOverflow() - top : height()) + ow * 2, prior);
546  } else {
547  if (firstLineBox() && firstLineBox()->topOverflow() < 0) {
548  int ow = style() ? style()->outlineSize() : 0;
549  repaintRectangle(-ow, -ow + firstLineBox()->topOverflow(),
550  effectiveWidth() + ow * 2, effectiveHeight() + ow * 2, prior);
551  } else {
552  return RenderBox::repaint(prior);
553  }
554  }
555 }
556 
557 int
558 RenderFlow::lowestPosition(bool includeOverflowInterior, bool includeSelf) const
559 {
560  int bottom = includeSelf && m_width > 0 ? m_height : 0;
561  if (!includeOverflowInterior && hasOverflowClip()) {
562  return bottom;
563  }
564 
565  // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids.
566  // For now, we have to descend into all the children, since we may have a huge abs div inside
567  // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to
568  // the abs div.
569  for (RenderObject *c = firstChild(); c; c = c->nextSibling()) {
570  if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) {
571  int lp = c->yPos() + c->lowestPosition(false);
572  bottom = qMax(bottom, lp);
573  }
574  }
575 
576  if (includeSelf && isRelPositioned()) {
577  int x = 0;
578  relativePositionOffset(x, bottom);
579  }
580 
581  return bottom;
582 }
583 
584 int RenderFlow::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const
585 {
586  int right = includeSelf && m_height > 0 ? m_width : 0;
587  if (!includeOverflowInterior && hasOverflowClip()) {
588  return right;
589  }
590 
591  // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids.
592  // For now, we have to descend into all the children, since we may have a huge abs div inside
593  // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to
594  // the abs div.
595  for (RenderObject *c = firstChild(); c; c = c->nextSibling()) {
596  if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) {
597  int rp = c->xPos() + c->rightmostPosition(false);
598  right = qMax(right, rp);
599  }
600  }
601 
602  if (includeSelf && isRelPositioned()) {
603  int y = 0;
604  relativePositionOffset(right, y);
605  }
606 
607  return right;
608 }
609 
610 int RenderFlow::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const
611 {
612  int left = includeSelf && m_height > 0 ? 0 : m_width;
613  if (!includeOverflowInterior && hasOverflowClip()) {
614  return left;
615  }
616 
617  // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids.
618  // For now, we have to descend into all the children, since we may have a huge abs div inside
619  // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to
620  // the abs div.
621  for (RenderObject *c = firstChild(); c; c = c->nextSibling()) {
622  if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) {
623  int lp = c->xPos() + c->leftmostPosition(false);
624  left = qMin(left, lp);
625  }
626  }
627 
628  if (includeSelf && isRelPositioned()) {
629  int y = 0;
630  relativePositionOffset(left, y);
631  }
632 
633  return left;
634 }
635 
636 int RenderFlow::highestPosition(bool includeOverflowInterior, bool includeSelf) const
637 {
638  int top = RenderBox::highestPosition(includeOverflowInterior, includeSelf);
639  if (!includeOverflowInterior && hasOverflowClip()) {
640  return top;
641  }
642 
643  // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids.
644  // For now, we have to descend into all the children, since we may have a huge abs div inside
645  // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to
646  // the abs div.
647  for (RenderObject *c = firstChild(); c; c = c->nextSibling()) {
648  if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) {
649  int hp = c->yPos() + c->highestPosition(false);
650  top = qMin(top, hp);
651  }
652  }
653 
654  if (includeSelf && isRelPositioned()) {
655  int x = 0;
656  relativePositionOffset(x, top);
657  }
658 
659  return top;
660 }
This file is part of the HTML rendering engine for KDE.
QTextStream & right(QTextStream &stream)
MESSAGECORE_EXPORT KMime::Content * next(KMime::Content *node, bool allowChildren=true)
KGuiItem cont()
QTextStream & left(QTextStream &stream)
void append(const T &value)
bool isEmpty() const const
MESSAGECORE_EXPORT KMime::Content * firstChild(const KMime::Content *node)
all geometry managing stuff is only in the block elements.
Definition: render_flow.h:44
This library provides a full-featured HTML parser and widget.
Base Class for all rendering tree objects.
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Jul 1 2020 22:44:37 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.