1 /*
2 * This file is part of the render object implementation for KHTML.
3 *
4 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5 * (C) 1999 Antti Koivisto (koivisto@kde.org)
6 * Copyright (C) 2003 Apple Computer, Inc.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25 #include "config.h"
26 #include "RenderFlexibleBox.h"
27
28 #include "RenderLayer.h"
29 #include "RenderView.h"
30 #include "TextRun.h"
31 #include <wtf/StdLibExtras.h>
32 #include <wtf/unicode/CharacterNames.h>
33
34 #ifdef ANDROID_LAYOUT
35 #include "Document.h"
36 #include "Settings.h"
37 #endif
38
39 using namespace std;
40
41 namespace WebCore {
42
43 class FlexBoxIterator {
44 public:
FlexBoxIterator(RenderFlexibleBox * parent)45 FlexBoxIterator(RenderFlexibleBox* parent)
46 {
47 box = parent;
48 if (box->style()->boxOrient() == HORIZONTAL && !box->style()->isLeftToRightDirection())
49 forward = box->style()->boxDirection() != BNORMAL;
50 else
51 forward = box->style()->boxDirection() == BNORMAL;
52 lastOrdinal = 1;
53 if (!forward) {
54 // No choice, since we're going backwards, we have to find out the highest ordinal up front.
55 RenderBox* child = box->firstChildBox();
56 while (child) {
57 if (child->style()->boxOrdinalGroup() > lastOrdinal)
58 lastOrdinal = child->style()->boxOrdinalGroup();
59 child = child->nextSiblingBox();
60 }
61 }
62
63 reset();
64 }
65
reset()66 void reset()
67 {
68 current = 0;
69 currentOrdinal = forward ? 0 : lastOrdinal+1;
70 }
71
first()72 RenderBox* first()
73 {
74 reset();
75 return next();
76 }
77
next()78 RenderBox* next()
79 {
80 do {
81 if (!current) {
82 if (forward) {
83 currentOrdinal++;
84 if (currentOrdinal > lastOrdinal)
85 return 0;
86 current = box->firstChildBox();
87 } else {
88 currentOrdinal--;
89 if (currentOrdinal == 0)
90 return 0;
91 current = box->lastChildBox();
92 }
93 }
94 else
95 current = forward ? current->nextSiblingBox() : current->previousSiblingBox();
96 if (current && current->style()->boxOrdinalGroup() > lastOrdinal)
97 lastOrdinal = current->style()->boxOrdinalGroup();
98 } while (!current || current->style()->boxOrdinalGroup() != currentOrdinal ||
99 current->style()->visibility() == COLLAPSE);
100 return current;
101 }
102
103 private:
104 RenderFlexibleBox* box;
105 RenderBox* current;
106 bool forward;
107 unsigned int currentOrdinal;
108 unsigned int lastOrdinal;
109 };
110
RenderFlexibleBox(Node * node)111 RenderFlexibleBox::RenderFlexibleBox(Node* node)
112 :RenderBlock(node)
113 {
114 setChildrenInline(false); // All of our children must be block-level
115 m_flexingChildren = m_stretchingChildren = false;
116 }
117
~RenderFlexibleBox()118 RenderFlexibleBox::~RenderFlexibleBox()
119 {
120 }
121
calcHorizontalPrefWidths()122 void RenderFlexibleBox::calcHorizontalPrefWidths()
123 {
124 for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) {
125 // positioned children don't affect the minmaxwidth
126 if (child->isPositioned() || child->style()->visibility() == COLLAPSE)
127 continue;
128
129 // A margin basically has three types: fixed, percentage, and auto (variable).
130 // Auto and percentage margins simply become 0 when computing min/max width.
131 // Fixed margins can be added in as is.
132 Length ml = child->style()->marginLeft();
133 Length mr = child->style()->marginRight();
134 int margin = 0, marginLeft = 0, marginRight = 0;
135 if (ml.isFixed())
136 marginLeft += ml.value();
137 if (mr.isFixed())
138 marginRight += mr.value();
139 margin = marginLeft + marginRight;
140
141 m_minPreferredLogicalWidth += child->minPreferredLogicalWidth() + margin;
142 m_maxPreferredLogicalWidth += child->maxPreferredLogicalWidth() + margin;
143 }
144 }
145
calcVerticalPrefWidths()146 void RenderFlexibleBox::calcVerticalPrefWidths()
147 {
148 for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) {
149 // Positioned children and collapsed children don't affect the min/max width
150 if (child->isPositioned() || child->style()->visibility() == COLLAPSE)
151 continue;
152
153 // A margin basically has three types: fixed, percentage, and auto (variable).
154 // Auto/percentage margins simply become 0 when computing min/max width.
155 // Fixed margins can be added in as is.
156 Length ml = child->style()->marginLeft();
157 Length mr = child->style()->marginRight();
158 int margin = 0;
159 if (ml.isFixed())
160 margin += ml.value();
161 if (mr.isFixed())
162 margin += mr.value();
163
164 int w = child->minPreferredLogicalWidth() + margin;
165 m_minPreferredLogicalWidth = max(w, m_minPreferredLogicalWidth);
166
167 w = child->maxPreferredLogicalWidth() + margin;
168 m_maxPreferredLogicalWidth = max(w, m_maxPreferredLogicalWidth);
169 }
170 }
171
computePreferredLogicalWidths()172 void RenderFlexibleBox::computePreferredLogicalWidths()
173 {
174 ASSERT(preferredLogicalWidthsDirty());
175
176 if (style()->width().isFixed() && style()->width().value() > 0)
177 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
178 else {
179 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = 0;
180
181 if (hasMultipleLines() || isVertical())
182 calcVerticalPrefWidths();
183 else
184 calcHorizontalPrefWidths();
185
186 m_maxPreferredLogicalWidth = max(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
187 }
188
189 if (hasOverflowClip() && style()->overflowY() == OSCROLL) {
190 layer()->setHasVerticalScrollbar(true);
191 int scrollbarWidth = verticalScrollbarWidth();
192 m_maxPreferredLogicalWidth += scrollbarWidth;
193 m_minPreferredLogicalWidth += scrollbarWidth;
194 }
195
196 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
197 m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
198 m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
199 }
200
201 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
202 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
203 m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
204 }
205
206 int borderAndPadding = borderAndPaddingLogicalWidth();
207 m_minPreferredLogicalWidth += borderAndPadding;
208 m_maxPreferredLogicalWidth += borderAndPadding;
209
210 setPreferredLogicalWidthsDirty(false);
211 }
212
layoutBlock(bool relayoutChildren,int)213 void RenderFlexibleBox::layoutBlock(bool relayoutChildren, int /*pageHeight FIXME: Implement */)
214 {
215 ASSERT(needsLayout());
216
217 if (!relayoutChildren && simplifiedLayout())
218 return;
219
220 LayoutRepainter repainter(*this, checkForRepaintDuringLayout());
221 LayoutStateMaintainer statePusher(view(), this, IntSize(x(), y()), hasTransform() || hasReflection() || style()->isFlippedBlocksWritingMode());
222
223 int previousWidth = width();
224 int previousHeight = height();
225
226 computeLogicalWidth();
227 computeLogicalHeight();
228
229 m_overflow.clear();
230
231 if (previousWidth != width() || previousHeight != height() ||
232 (parent()->isFlexibleBox() && parent()->style()->boxOrient() == HORIZONTAL &&
233 parent()->style()->boxAlign() == BSTRETCH))
234 relayoutChildren = true;
235
236 #ifdef ANDROID_LAYOUT
237 checkAndSetRelayoutChildren(&relayoutChildren);
238 #endif
239 setHeight(0);
240
241 m_flexingChildren = m_stretchingChildren = false;
242
243 initMaxMarginValues();
244
245 // For overflow:scroll blocks, ensure we have both scrollbars in place always.
246 if (scrollsOverflow()) {
247 if (style()->overflowX() == OSCROLL)
248 layer()->setHasHorizontalScrollbar(true);
249 if (style()->overflowY() == OSCROLL)
250 layer()->setHasVerticalScrollbar(true);
251 }
252
253 if (isHorizontal())
254 layoutHorizontalBox(relayoutChildren);
255 else
256 layoutVerticalBox(relayoutChildren);
257
258 int oldClientAfterEdge = clientLogicalBottom();
259 computeLogicalHeight();
260
261 if (previousHeight != height())
262 relayoutChildren = true;
263
264 layoutPositionedObjects(relayoutChildren || isRoot());
265
266 if (!isFloatingOrPositioned() && height() == 0) {
267 // We are a block with no border and padding and a computed height
268 // of 0. The CSS spec states that zero-height blocks collapse their margins
269 // together.
270 // When blocks are self-collapsing, we just use the top margin values and set the
271 // bottom margin max values to 0. This way we don't factor in the values
272 // twice when we collapse with our previous vertically adjacent and
273 // following vertically adjacent blocks.
274 int pos = maxPositiveMarginBefore();
275 int neg = maxNegativeMarginBefore();
276 if (maxPositiveMarginAfter() > pos)
277 pos = maxPositiveMarginAfter();
278 if (maxNegativeMarginAfter() > neg)
279 neg = maxNegativeMarginAfter();
280 setMaxMarginBeforeValues(pos, neg);
281 setMaxMarginAfterValues(0, 0);
282 }
283
284 computeOverflow(oldClientAfterEdge);
285
286 statePusher.pop();
287
288 updateLayerTransform();
289
290 if (view()->layoutState()->pageLogicalHeight())
291 setPageLogicalOffset(view()->layoutState()->pageLogicalOffset(logicalTop()));
292
293 // Update our scrollbars if we're overflow:auto/scroll/hidden now that we know if
294 // we overflow or not.
295 if (hasOverflowClip())
296 layer()->updateScrollInfoAfterLayout();
297
298 // Repaint with our new bounds if they are different from our old bounds.
299 repainter.repaintAfterLayout();
300
301 setNeedsLayout(false);
302 }
303
304 // The first walk over our kids is to find out if we have any flexible children.
gatherFlexChildrenInfo(FlexBoxIterator & iterator,bool relayoutChildren,unsigned int & highestFlexGroup,unsigned int & lowestFlexGroup,bool & haveFlex)305 static void gatherFlexChildrenInfo(FlexBoxIterator& iterator, bool relayoutChildren, unsigned int& highestFlexGroup, unsigned int& lowestFlexGroup, bool& haveFlex)
306 {
307 RenderBox* child = iterator.first();
308 while (child) {
309 // Check to see if this child flexes.
310 if (!child->isPositioned() && child->style()->boxFlex() > 0.0f) {
311 // We always have to lay out flexible objects again, since the flex distribution
312 // may have changed, and we need to reallocate space.
313 child->setOverrideSize(-1);
314 if (!relayoutChildren)
315 child->setChildNeedsLayout(true, false);
316 haveFlex = true;
317 unsigned int flexGroup = child->style()->boxFlexGroup();
318 if (lowestFlexGroup == 0)
319 lowestFlexGroup = flexGroup;
320 if (flexGroup < lowestFlexGroup)
321 lowestFlexGroup = flexGroup;
322 if (flexGroup > highestFlexGroup)
323 highestFlexGroup = flexGroup;
324 }
325 child = iterator.next();
326 }
327 }
328
layoutHorizontalBox(bool relayoutChildren)329 void RenderFlexibleBox::layoutHorizontalBox(bool relayoutChildren)
330 {
331 int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight();
332 int yPos = borderTop() + paddingTop();
333 int xPos = borderLeft() + paddingLeft();
334 bool heightSpecified = false;
335 int oldHeight = 0;
336
337 int remainingSpace = 0;
338
339
340 FlexBoxIterator iterator(this);
341 unsigned int highestFlexGroup = 0;
342 unsigned int lowestFlexGroup = 0;
343 bool haveFlex = false;
344 gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex);
345
346 RenderBox* child;
347
348 RenderBlock::startDelayUpdateScrollInfo();
349
350 // We do 2 passes. The first pass is simply to lay everyone out at
351 // their preferred widths. The second pass handles flexing the children.
352 do {
353 // Reset our height.
354 setHeight(yPos);
355
356 xPos = borderLeft() + paddingLeft();
357
358 // Our first pass is done without flexing. We simply lay the children
359 // out within the box. We have to do a layout first in order to determine
360 // our box's intrinsic height.
361 int maxAscent = 0, maxDescent = 0;
362 child = iterator.first();
363 while (child) {
364 // make sure we relayout children if we need it.
365 if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())))
366 child->setChildNeedsLayout(true, false);
367
368 if (child->isPositioned()) {
369 child = iterator.next();
370 continue;
371 }
372
373 // Compute the child's vertical margins.
374 child->computeBlockDirectionMargins(this);
375
376 if (!child->needsLayout())
377 child->markForPaginationRelayoutIfNeeded();
378
379 // Now do the layout.
380 child->layoutIfNeeded();
381
382 // Update our height and overflow height.
383 if (style()->boxAlign() == BBASELINE) {
384 int ascent = child->firstLineBoxBaseline();
385 if (ascent == -1)
386 ascent = child->height() + child->marginBottom();
387 ascent += child->marginTop();
388 int descent = (child->marginTop() + child->height() + child->marginBottom()) - ascent;
389
390 // Update our maximum ascent.
391 maxAscent = max(maxAscent, ascent);
392
393 // Update our maximum descent.
394 maxDescent = max(maxDescent, descent);
395
396 // Now update our height.
397 setHeight(max(yPos + maxAscent + maxDescent, height()));
398 }
399 else
400 setHeight(max(height(), yPos + child->marginTop() + child->height() + child->marginBottom()));
401
402 child = iterator.next();
403 }
404
405 if (!iterator.first() && hasLineIfEmpty())
406 setHeight(height() + lineHeight(true, style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes));
407
408 setHeight(height() + toAdd);
409
410 oldHeight = height();
411 computeLogicalHeight();
412
413 relayoutChildren = false;
414 if (oldHeight != height())
415 heightSpecified = true;
416
417 // Now that our height is actually known, we can place our boxes.
418 m_stretchingChildren = (style()->boxAlign() == BSTRETCH);
419 child = iterator.first();
420 while (child) {
421 if (child->isPositioned()) {
422 child->containingBlock()->insertPositionedObject(child);
423 RenderLayer* childLayer = child->layer();
424 childLayer->setStaticInlinePosition(xPos);
425 if (childLayer->staticBlockPosition() != yPos) {
426 childLayer->setStaticBlockPosition(yPos);
427 if (child->style()->hasStaticBlockPosition(style()->isHorizontalWritingMode()))
428 child->setChildNeedsLayout(true, false);
429 }
430 child = iterator.next();
431 continue;
432 }
433
434 // We need to see if this child's height has changed, since we make block elements
435 // fill the height of a containing box by default.
436 // Now do a layout.
437 int oldChildHeight = child->height();
438 child->computeLogicalHeight();
439 if (oldChildHeight != child->height())
440 child->setChildNeedsLayout(true, false);
441
442 if (!child->needsLayout())
443 child->markForPaginationRelayoutIfNeeded();
444
445 child->layoutIfNeeded();
446
447 // We can place the child now, using our value of box-align.
448 xPos += child->marginLeft();
449 int childY = yPos;
450 switch (style()->boxAlign()) {
451 case BCENTER:
452 childY += child->marginTop() + max(0, (contentHeight() - (child->height() + child->marginTop() + child->marginBottom()))/2);
453 break;
454 case BBASELINE: {
455 int ascent = child->firstLineBoxBaseline();
456 if (ascent == -1)
457 ascent = child->height() + child->marginBottom();
458 ascent += child->marginTop();
459 childY += child->marginTop() + (maxAscent - ascent);
460 break;
461 }
462 case BEND:
463 childY += contentHeight() - child->marginBottom() - child->height();
464 break;
465 default: // BSTART
466 childY += child->marginTop();
467 break;
468 }
469
470 placeChild(child, xPos, childY);
471
472 xPos += child->width() + child->marginRight();
473
474 child = iterator.next();
475 }
476
477 remainingSpace = borderLeft() + paddingLeft() + contentWidth() - xPos;
478
479 m_stretchingChildren = false;
480 if (m_flexingChildren)
481 haveFlex = false; // We're done.
482 else if (haveFlex) {
483 // We have some flexible objects. See if we need to grow/shrink them at all.
484 if (!remainingSpace)
485 break;
486
487 // Allocate the remaining space among the flexible objects. If we are trying to
488 // grow, then we go from the lowest flex group to the highest flex group. For shrinking,
489 // we go from the highest flex group to the lowest group.
490 bool expanding = remainingSpace > 0;
491 unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup;
492 unsigned int end = expanding? highestFlexGroup : lowestFlexGroup;
493 for (unsigned int i = start; i <= end && remainingSpace; i++) {
494 // Always start off by assuming the group can get all the remaining space.
495 int groupRemainingSpace = remainingSpace;
496 do {
497 // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width
498 // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and
499 // computing the allowed growth before an object hits its min/max width (and thus
500 // forces a totalFlex recomputation).
501 int groupRemainingSpaceAtBeginning = groupRemainingSpace;
502 float totalFlex = 0.0f;
503 child = iterator.first();
504 while (child) {
505 if (allowedChildFlex(child, expanding, i))
506 totalFlex += child->style()->boxFlex();
507 child = iterator.next();
508 }
509 child = iterator.first();
510 int spaceAvailableThisPass = groupRemainingSpace;
511 while (child) {
512 int allowedFlex = allowedChildFlex(child, expanding, i);
513 if (allowedFlex) {
514 int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex()));
515 spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex);
516 }
517 child = iterator.next();
518 }
519
520 // The flex groups may not have any flexible objects this time around.
521 if (!spaceAvailableThisPass || totalFlex == 0.0f) {
522 // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group.
523 groupRemainingSpace = 0;
524 continue;
525 }
526
527 // Now distribute the space to objects.
528 child = iterator.first();
529 while (child && spaceAvailableThisPass && totalFlex) {
530 if (allowedChildFlex(child, expanding, i)) {
531 int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex));
532 if (spaceAdd) {
533 child->setOverrideSize(child->overrideWidth() + spaceAdd);
534 m_flexingChildren = true;
535 relayoutChildren = true;
536 }
537
538 spaceAvailableThisPass -= spaceAdd;
539 remainingSpace -= spaceAdd;
540 groupRemainingSpace -= spaceAdd;
541
542 totalFlex -= child->style()->boxFlex();
543 }
544 child = iterator.next();
545 }
546 if (groupRemainingSpace == groupRemainingSpaceAtBeginning) {
547 // this is not advancing, avoid getting stuck by distributing the remaining pixels
548 child = iterator.first();
549 int spaceAdd = groupRemainingSpace > 0 ? 1 : -1;
550 while (child && groupRemainingSpace) {
551 if (allowedChildFlex(child, expanding, i)) {
552 child->setOverrideSize(child->overrideWidth() + spaceAdd);
553 m_flexingChildren = true;
554 relayoutChildren = true;
555 remainingSpace -= spaceAdd;
556 groupRemainingSpace -= spaceAdd;
557 }
558 child = iterator.next();
559 }
560 }
561 } while (groupRemainingSpace);
562 }
563
564 // We didn't find any children that could grow.
565 if (haveFlex && !m_flexingChildren)
566 haveFlex = false;
567 }
568 } while (haveFlex);
569
570 m_flexingChildren = false;
571
572 RenderBlock::finishDelayUpdateScrollInfo();
573
574 if (remainingSpace > 0 && ((style()->isLeftToRightDirection() && style()->boxPack() != BSTART)
575 || (!style()->isLeftToRightDirection() && style()->boxPack() != BEND))) {
576 // Children must be repositioned.
577 int offset = 0;
578 if (style()->boxPack() == BJUSTIFY) {
579 // Determine the total number of children.
580 int totalChildren = 0;
581 child = iterator.first();
582 while (child) {
583 if (child->isPositioned()) {
584 child = iterator.next();
585 continue;
586 }
587 totalChildren++;
588 child = iterator.next();
589 }
590
591 // Iterate over the children and space them out according to the
592 // justification level.
593 if (totalChildren > 1) {
594 totalChildren--;
595 bool firstChild = true;
596 child = iterator.first();
597 while (child) {
598 if (child->isPositioned()) {
599 child = iterator.next();
600 continue;
601 }
602
603 if (firstChild) {
604 firstChild = false;
605 child = iterator.next();
606 continue;
607 }
608
609 offset += remainingSpace/totalChildren;
610 remainingSpace -= (remainingSpace/totalChildren);
611 totalChildren--;
612
613 placeChild(child, child->x()+offset, child->y());
614 child = iterator.next();
615 }
616 }
617 } else {
618 if (style()->boxPack() == BCENTER)
619 offset += remainingSpace/2;
620 else // END for LTR, START for RTL
621 offset += remainingSpace;
622 child = iterator.first();
623 while (child) {
624 if (child->isPositioned()) {
625 child = iterator.next();
626 continue;
627 }
628 placeChild(child, child->x()+offset, child->y());
629 child = iterator.next();
630 }
631 }
632 }
633
634 // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of
635 // a height change, we revert our height back to the intrinsic height before returning.
636 if (heightSpecified)
637 setHeight(oldHeight);
638 }
639
layoutVerticalBox(bool relayoutChildren)640 void RenderFlexibleBox::layoutVerticalBox(bool relayoutChildren)
641 {
642 int xPos = borderLeft() + paddingLeft();
643 int yPos = borderTop() + paddingTop();
644 if (!style()->isLeftToRightDirection())
645 xPos = width() - paddingRight() - borderRight();
646 int toAdd = borderBottom() + paddingBottom() + horizontalScrollbarHeight();
647 bool heightSpecified = false;
648 int oldHeight = 0;
649
650 int remainingSpace = 0;
651
652 FlexBoxIterator iterator(this);
653 unsigned int highestFlexGroup = 0;
654 unsigned int lowestFlexGroup = 0;
655 bool haveFlex = false;
656 gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, lowestFlexGroup, haveFlex);
657
658 RenderBox* child;
659
660 // We confine the line clamp ugliness to vertical flexible boxes (thus keeping it out of
661 // mainstream block layout); this is not really part of the XUL box model.
662 bool haveLineClamp = !style()->lineClamp().isNone();
663 if (haveLineClamp)
664 applyLineClamp(iterator, relayoutChildren);
665
666 RenderBlock::startDelayUpdateScrollInfo();
667
668 // We do 2 passes. The first pass is simply to lay everyone out at
669 // their preferred widths. The second pass handles flexing the children.
670 // Our first pass is done without flexing. We simply lay the children
671 // out within the box.
672 do {
673 setHeight(borderTop() + paddingTop());
674 int minHeight = height() + toAdd;
675
676 child = iterator.first();
677 while (child) {
678 // make sure we relayout children if we need it.
679 if (!haveLineClamp && (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent()))))
680 child->setChildNeedsLayout(true, false);
681
682 if (child->isPositioned()) {
683 child->containingBlock()->insertPositionedObject(child);
684 RenderLayer* childLayer = child->layer();
685 childLayer->setStaticInlinePosition(borderStart() + paddingStart());
686 if (childLayer->staticBlockPosition() != height()) {
687 childLayer->setStaticBlockPosition(height());
688 if (child->style()->hasStaticBlockPosition(style()->isHorizontalWritingMode()))
689 child->setChildNeedsLayout(true, false);
690 }
691 child = iterator.next();
692 continue;
693 }
694
695 // Compute the child's vertical margins.
696 child->computeBlockDirectionMargins(this);
697
698 // Add in the child's marginTop to our height.
699 setHeight(height() + child->marginTop());
700
701 if (!child->needsLayout())
702 child->markForPaginationRelayoutIfNeeded();
703
704 // Now do a layout.
705 child->layoutIfNeeded();
706
707 // We can place the child now, using our value of box-align.
708 int childX = borderLeft() + paddingLeft();
709 switch (style()->boxAlign()) {
710 case BCENTER:
711 case BBASELINE: // Baseline just maps to center for vertical boxes
712 childX += child->marginLeft() + max(0, (contentWidth() - (child->width() + child->marginLeft() + child->marginRight()))/2);
713 break;
714 case BEND:
715 if (!style()->isLeftToRightDirection())
716 childX += child->marginLeft();
717 else
718 childX += contentWidth() - child->marginRight() - child->width();
719 break;
720 default: // BSTART/BSTRETCH
721 if (style()->isLeftToRightDirection())
722 childX += child->marginLeft();
723 else
724 childX += contentWidth() - child->marginRight() - child->width();
725 break;
726 }
727
728 // Place the child.
729 placeChild(child, childX, height());
730 setHeight(height() + child->height() + child->marginBottom());
731
732 child = iterator.next();
733 }
734
735 yPos = height();
736
737 if (!iterator.first() && hasLineIfEmpty())
738 setHeight(height() + lineHeight(true, style()->isHorizontalWritingMode() ? HorizontalLine : VerticalLine, PositionOfInteriorLineBoxes));
739
740 setHeight(height() + toAdd);
741
742 // Negative margins can cause our height to shrink below our minimal height (border/padding).
743 // If this happens, ensure that the computed height is increased to the minimal height.
744 if (height() < minHeight)
745 setHeight(minHeight);
746
747 // Now we have to calc our height, so we know how much space we have remaining.
748 oldHeight = height();
749 computeLogicalHeight();
750 if (oldHeight != height())
751 heightSpecified = true;
752
753 remainingSpace = borderTop() + paddingTop() + contentHeight() - yPos;
754
755 if (m_flexingChildren)
756 haveFlex = false; // We're done.
757 else if (haveFlex) {
758 // We have some flexible objects. See if we need to grow/shrink them at all.
759 if (!remainingSpace)
760 break;
761
762 // Allocate the remaining space among the flexible objects. If we are trying to
763 // grow, then we go from the lowest flex group to the highest flex group. For shrinking,
764 // we go from the highest flex group to the lowest group.
765 bool expanding = remainingSpace > 0;
766 unsigned int start = expanding ? lowestFlexGroup : highestFlexGroup;
767 unsigned int end = expanding? highestFlexGroup : lowestFlexGroup;
768 for (unsigned int i = start; i <= end && remainingSpace; i++) {
769 // Always start off by assuming the group can get all the remaining space.
770 int groupRemainingSpace = remainingSpace;
771 do {
772 // Flexing consists of multiple passes, since we have to change ratios every time an object hits its max/min-width
773 // For a given pass, we always start off by computing the totalFlex of all objects that can grow/shrink at all, and
774 // computing the allowed growth before an object hits its min/max width (and thus
775 // forces a totalFlex recomputation).
776 int groupRemainingSpaceAtBeginning = groupRemainingSpace;
777 float totalFlex = 0.0f;
778 child = iterator.first();
779 while (child) {
780 if (allowedChildFlex(child, expanding, i))
781 totalFlex += child->style()->boxFlex();
782 child = iterator.next();
783 }
784 child = iterator.first();
785 int spaceAvailableThisPass = groupRemainingSpace;
786 while (child) {
787 int allowedFlex = allowedChildFlex(child, expanding, i);
788 if (allowedFlex) {
789 int projectedFlex = (allowedFlex == INT_MAX) ? allowedFlex : (int)(allowedFlex * (totalFlex / child->style()->boxFlex()));
790 spaceAvailableThisPass = expanding ? min(spaceAvailableThisPass, projectedFlex) : max(spaceAvailableThisPass, projectedFlex);
791 }
792 child = iterator.next();
793 }
794
795 // The flex groups may not have any flexible objects this time around.
796 if (!spaceAvailableThisPass || totalFlex == 0.0f) {
797 // If we just couldn't grow/shrink any more, then it's time to transition to the next flex group.
798 groupRemainingSpace = 0;
799 continue;
800 }
801
802 // Now distribute the space to objects.
803 child = iterator.first();
804 while (child && spaceAvailableThisPass && totalFlex) {
805 if (allowedChildFlex(child, expanding, i)) {
806 int spaceAdd = (int)(spaceAvailableThisPass * (child->style()->boxFlex()/totalFlex));
807 if (spaceAdd) {
808 child->setOverrideSize(child->overrideHeight() + spaceAdd);
809 m_flexingChildren = true;
810 relayoutChildren = true;
811 }
812
813 spaceAvailableThisPass -= spaceAdd;
814 remainingSpace -= spaceAdd;
815 groupRemainingSpace -= spaceAdd;
816
817 totalFlex -= child->style()->boxFlex();
818 }
819 child = iterator.next();
820 }
821 if (groupRemainingSpace == groupRemainingSpaceAtBeginning) {
822 // this is not advancing, avoid getting stuck by distributing the remaining pixels
823 child = iterator.first();
824 int spaceAdd = groupRemainingSpace > 0 ? 1 : -1;
825 while (child && groupRemainingSpace) {
826 if (allowedChildFlex(child, expanding, i)) {
827 child->setOverrideSize(child->overrideHeight() + spaceAdd);
828 m_flexingChildren = true;
829 relayoutChildren = true;
830 remainingSpace -= spaceAdd;
831 groupRemainingSpace -= spaceAdd;
832 }
833 child = iterator.next();
834 }
835 }
836 } while (groupRemainingSpace);
837 }
838
839 // We didn't find any children that could grow.
840 if (haveFlex && !m_flexingChildren)
841 haveFlex = false;
842 }
843 } while (haveFlex);
844
845 RenderBlock::finishDelayUpdateScrollInfo();
846
847 if (style()->boxPack() != BSTART && remainingSpace > 0) {
848 // Children must be repositioned.
849 int offset = 0;
850 if (style()->boxPack() == BJUSTIFY) {
851 // Determine the total number of children.
852 int totalChildren = 0;
853 child = iterator.first();
854 while (child) {
855 if (child->isPositioned()) {
856 child = iterator.next();
857 continue;
858 }
859 totalChildren++;
860 child = iterator.next();
861 }
862
863 // Iterate over the children and space them out according to the
864 // justification level.
865 if (totalChildren > 1) {
866 totalChildren--;
867 bool firstChild = true;
868 child = iterator.first();
869 while (child) {
870 if (child->isPositioned()) {
871 child = iterator.next();
872 continue;
873 }
874
875 if (firstChild) {
876 firstChild = false;
877 child = iterator.next();
878 continue;
879 }
880
881 offset += remainingSpace/totalChildren;
882 remainingSpace -= (remainingSpace/totalChildren);
883 totalChildren--;
884 placeChild(child, child->x(), child->y()+offset);
885 child = iterator.next();
886 }
887 }
888 } else {
889 if (style()->boxPack() == BCENTER)
890 offset += remainingSpace/2;
891 else // END
892 offset += remainingSpace;
893 child = iterator.first();
894 while (child) {
895 if (child->isPositioned()) {
896 child = iterator.next();
897 continue;
898 }
899 placeChild(child, child->x(), child->y()+offset);
900 child = iterator.next();
901 }
902 }
903 }
904
905 // So that the computeLogicalHeight in layoutBlock() knows to relayout positioned objects because of
906 // a height change, we revert our height back to the intrinsic height before returning.
907 if (heightSpecified)
908 setHeight(oldHeight);
909 }
910
applyLineClamp(FlexBoxIterator & iterator,bool relayoutChildren)911 void RenderFlexibleBox::applyLineClamp(FlexBoxIterator& iterator, bool relayoutChildren)
912 {
913 int maxLineCount = 0;
914 for (RenderBox* child = iterator.first(); child; child = iterator.next()) {
915 if (child->isPositioned())
916 continue;
917
918 if (relayoutChildren || (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent()))
919 || (child->style()->height().isAuto() && child->isBlockFlow())) {
920 child->setChildNeedsLayout(true, false);
921
922 // Dirty all the positioned objects.
923 if (child->isRenderBlock()) {
924 toRenderBlock(child)->markPositionedObjectsForLayout();
925 toRenderBlock(child)->clearTruncation();
926 }
927 }
928 child->layoutIfNeeded();
929 if (child->style()->height().isAuto() && child->isBlockFlow())
930 maxLineCount = max(maxLineCount, toRenderBlock(child)->lineCount());
931 }
932
933 // Get the number of lines and then alter all block flow children with auto height to use the
934 // specified height. We always try to leave room for at least one line.
935 LineClampValue lineClamp = style()->lineClamp();
936 int numVisibleLines = lineClamp.isPercentage() ? max(1, (maxLineCount + 1) * lineClamp.value() / 100) : lineClamp.value();
937 if (numVisibleLines >= maxLineCount)
938 return;
939
940 for (RenderBox* child = iterator.first(); child; child = iterator.next()) {
941 if (child->isPositioned() || !child->style()->height().isAuto() || !child->isBlockFlow())
942 continue;
943
944 RenderBlock* blockChild = toRenderBlock(child);
945 int lineCount = blockChild->lineCount();
946 if (lineCount <= numVisibleLines)
947 continue;
948
949 int newHeight = blockChild->heightForLineCount(numVisibleLines);
950 if (newHeight == child->height())
951 continue;
952
953 child->setChildNeedsLayout(true, false);
954 child->setOverrideSize(newHeight);
955 m_flexingChildren = true;
956 child->layoutIfNeeded();
957 m_flexingChildren = false;
958 child->setOverrideSize(-1);
959
960 // FIXME: For now don't support RTL.
961 if (style()->direction() != LTR)
962 continue;
963
964 // Get the last line
965 RootInlineBox* lastLine = blockChild->lineAtIndex(lineCount-1);
966 if (!lastLine)
967 continue;
968
969 RootInlineBox* lastVisibleLine = blockChild->lineAtIndex(numVisibleLines-1);
970 if (!lastVisibleLine)
971 continue;
972
973 const UChar ellipsisAndSpace[2] = { horizontalEllipsis, ' ' };
974 DEFINE_STATIC_LOCAL(AtomicString, ellipsisAndSpaceStr, (ellipsisAndSpace, 2));
975 DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, (&horizontalEllipsis, 1));
976 const Font& font = style(numVisibleLines == 1)->font();
977
978 // Get ellipsis width, and if the last child is an anchor, it will go after the ellipsis, so add in a space and the anchor width too
979 int totalWidth;
980 InlineBox* anchorBox = lastLine->lastChild();
981 if (anchorBox && anchorBox->renderer()->style()->isLink())
982 totalWidth = anchorBox->logicalWidth() + font.width(TextRun(ellipsisAndSpace, 2));
983 else {
984 anchorBox = 0;
985 totalWidth = font.width(TextRun(&horizontalEllipsis, 1));
986 }
987
988 // See if this width can be accommodated on the last visible line
989 RenderBlock* destBlock = toRenderBlock(lastVisibleLine->renderer());
990 RenderBlock* srcBlock = toRenderBlock(lastLine->renderer());
991
992 // FIXME: Directions of src/destBlock could be different from our direction and from one another.
993 if (!srcBlock->style()->isLeftToRightDirection())
994 continue;
995
996 bool leftToRight = destBlock->style()->isLeftToRightDirection();
997 if (!leftToRight)
998 continue;
999
1000 int blockRightEdge = destBlock->logicalRightOffsetForLine(lastVisibleLine->y(), false);
1001 int blockLeftEdge = destBlock->logicalLeftOffsetForLine(lastVisibleLine->y(), false);
1002
1003 int blockEdge = leftToRight ? blockRightEdge : blockLeftEdge;
1004 if (!lastVisibleLine->lineCanAccommodateEllipsis(leftToRight, blockEdge, lastVisibleLine->x() + lastVisibleLine->logicalWidth(), totalWidth))
1005 continue;
1006
1007 // Let the truncation code kick in.
1008 lastVisibleLine->placeEllipsis(anchorBox ? ellipsisAndSpaceStr : ellipsisStr, leftToRight, blockLeftEdge, blockRightEdge, totalWidth, anchorBox);
1009 destBlock->setHasMarkupTruncation(true);
1010 }
1011 }
1012
placeChild(RenderBox * child,int x,int y)1013 void RenderFlexibleBox::placeChild(RenderBox* child, int x, int y)
1014 {
1015 IntRect oldRect(child->x(), child->y() , child->width(), child->height());
1016
1017 // Place the child.
1018 child->setLocation(x, y);
1019
1020 // If the child moved, we have to repaint it as well as any floating/positioned
1021 // descendants. An exception is if we need a layout. In this case, we know we're going to
1022 // repaint ourselves (and the child) anyway.
1023 if (!selfNeedsLayout() && child->checkForRepaintDuringLayout())
1024 child->repaintDuringLayoutIfMoved(oldRect);
1025 }
1026
allowedChildFlex(RenderBox * child,bool expanding,unsigned int group)1027 int RenderFlexibleBox::allowedChildFlex(RenderBox* child, bool expanding, unsigned int group)
1028 {
1029 if (child->isPositioned() || child->style()->boxFlex() == 0.0f || child->style()->boxFlexGroup() != group)
1030 return 0;
1031
1032 if (expanding) {
1033 if (isHorizontal()) {
1034 // FIXME: For now just handle fixed values.
1035 int maxW = INT_MAX;
1036 int w = child->overrideWidth() - child->borderAndPaddingWidth();
1037 if (!child->style()->maxWidth().isUndefined() &&
1038 child->style()->maxWidth().isFixed())
1039 maxW = child->style()->maxWidth().value();
1040 else if (child->style()->maxWidth().type() == Intrinsic)
1041 maxW = child->maxPreferredLogicalWidth();
1042 else if (child->style()->maxWidth().type() == MinIntrinsic)
1043 maxW = child->minPreferredLogicalWidth();
1044 if (maxW == INT_MAX)
1045 return maxW;
1046 return max(0, maxW - w);
1047 } else {
1048 // FIXME: For now just handle fixed values.
1049 int maxH = INT_MAX;
1050 int h = child->overrideHeight() - child->borderAndPaddingHeight();
1051 if (!child->style()->maxHeight().isUndefined() &&
1052 child->style()->maxHeight().isFixed())
1053 maxH = child->style()->maxHeight().value();
1054 if (maxH == INT_MAX)
1055 return maxH;
1056 return max(0, maxH - h);
1057 }
1058 }
1059
1060 // FIXME: For now just handle fixed values.
1061 if (isHorizontal()) {
1062 int minW = child->minPreferredLogicalWidth();
1063 int w = child->overrideWidth() - child->borderAndPaddingWidth();
1064 if (child->style()->minWidth().isFixed())
1065 minW = child->style()->minWidth().value();
1066 else if (child->style()->minWidth().type() == Intrinsic)
1067 minW = child->maxPreferredLogicalWidth();
1068 else if (child->style()->minWidth().type() == MinIntrinsic)
1069 minW = child->minPreferredLogicalWidth();
1070
1071 int allowedShrinkage = min(0, minW - w);
1072 return allowedShrinkage;
1073 } else {
1074 if (child->style()->minHeight().isFixed()) {
1075 int minH = child->style()->minHeight().value();
1076 int h = child->overrideHeight() - child->borderAndPaddingHeight();
1077 int allowedShrinkage = min(0, minH - h);
1078 return allowedShrinkage;
1079 }
1080 }
1081
1082 return 0;
1083 }
1084
renderName() const1085 const char *RenderFlexibleBox::renderName() const
1086 {
1087 if (isFloating())
1088 return "RenderFlexibleBox (floating)";
1089 if (isPositioned())
1090 return "RenderFlexibleBox (positioned)";
1091 if (isAnonymous())
1092 return "RenderFlexibleBox (generated)";
1093 if (isRelPositioned())
1094 return "RenderFlexibleBox (relative positioned)";
1095 return "RenderFlexibleBox";
1096 }
1097
1098 } // namespace WebCore
1099