void RenderBlockFlow::layoutBlockChildren(bool relayoutChildren, SubtreeLayoutScope& layoutScope, LayoutUnit beforeEdge, LayoutUnit afterEdge) { dirtyForLayoutFromPercentageHeightDescendants(layoutScope); RenderBox* next = firstChildBox(); RenderBox* lastNormalFlowChild = 0; while (next) { RenderBox* child = next; next = child->nextSiblingBox(); // FIXME: this should only be set from clearNeedsLayout crbug.com/361250 child->setLayoutDidGetCalled(true); updateBlockChildDirtyBitsBeforeLayout(relayoutChildren, child); if (child->isOutOfFlowPositioned()) { child->containingBlock()->insertPositionedObject(child); adjustPositionedBlock(child); continue; } // Lay out the child. layoutBlockChild(child); lastNormalFlowChild = child; } // Negative margins can cause our height to shrink below our minimal height (border/padding). // If this happens, ensure that the computed height is increased to the minimal height. setLogicalHeight(std::max(logicalHeight() + afterEdge, beforeEdge + afterEdge)); }
RenderObject* RenderMultiColumnBlock::layoutSpecialExcludedChild(bool relayoutChildren) { if (!m_flowThread) return 0; // Update the sizes of our regions (but not the placement) before we lay out the flow thread. // FIXME: Eventually this is going to get way more complicated, and we will be destroying regions // instead of trying to keep them around. bool shouldInvalidateRegions = false; for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) { if (childBox == m_flowThread) continue; if (relayoutChildren || childBox->needsLayout()) { childBox->updateLogicalWidth(); childBox->updateLogicalHeight(); shouldInvalidateRegions = true; } } if (shouldInvalidateRegions) m_flowThread->invalidateRegions(); if (relayoutChildren) m_flowThread->setChildNeedsLayout(true, MarkOnlyThis); setLogicalTopForChild(m_flowThread, borderBefore() + paddingBefore()); m_flowThread->layoutIfNeeded(); determineLogicalLeftPositionForChild(m_flowThread); return m_flowThread; }
RenderObject* RenderMultiColumnBlock::layoutSpecialExcludedChild(bool relayoutChildren) { if (!m_flowThread) return 0; // Update the dimensions of our regions before we lay out the flow thread. // FIXME: Eventually this is going to get way more complicated, and we will be destroying regions // instead of trying to keep them around. bool shouldInvalidateRegions = false; for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) { if (childBox == m_flowThread) continue; if (relayoutChildren || childBox->needsLayout()) { if (!m_inBalancingPass && childBox->isRenderMultiColumnSet()) toRenderMultiColumnSet(childBox)->prepareForLayout(); shouldInvalidateRegions = true; } } if (shouldInvalidateRegions) m_flowThread->invalidateRegions(); if (relayoutChildren) m_flowThread->setChildNeedsLayout(MarkOnlyThis); setLogicalTopForChild(*m_flowThread, borderAndPaddingBefore()); m_flowThread->layoutIfNeeded(); determineLogicalLeftPositionForChild(*m_flowThread); return m_flowThread; }
void RenderMathMLPadded::layoutBlock(bool relayoutChildren, LayoutUnit) { ASSERT(needsLayout()); if (!relayoutChildren && simplifiedLayout()) return; // We first layout our children as a normal <mrow> element. LayoutUnit contentAscent, contentDescent, contentWidth; contentAscent = contentDescent = 0; RenderMathMLRow::computeLineVerticalStretch(contentAscent, contentDescent); RenderMathMLRow::layoutRowItems(contentAscent, contentDescent); contentWidth = logicalWidth(); // We parse the mpadded attributes using the content metrics as the default value. LayoutUnit width = contentWidth; LayoutUnit ascent = contentAscent; LayoutUnit descent = contentDescent; LayoutUnit lspace = 0; LayoutUnit voffset = 0; resolveAttributes(width, ascent, descent, lspace, voffset); // Align children on the new baseline and shift them by (lspace, -voffset) LayoutPoint contentLocation(lspace, ascent - contentAscent - voffset); for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) child->setLocation(child->location() + contentLocation); // Set the final metrics. setLogicalWidth(width); m_ascent = ascent; setLogicalHeight(ascent + descent); clearNeedsLayout(); }
bool RenderMathMLFraction::isValid() const { // Verify whether the list of children is valid: // <mfrac> numerator denominator </mfrac> auto* child = firstChildBox(); if (!child) return false; child = child->nextSiblingBox(); return child && !child->nextSiblingBox(); }
int RenderView::docWidth() const { int w = rightmostPosition(); for (RenderBox* c = firstChildBox(); c; c = c->nextSiblingBox()) { int dw = c->width() + c->marginLeft() + c->marginRight(); if (dw > w) w = dw; } return w; }
int RenderView::docWidth(int leftOverflow) const { int w = m_bodyIsLTR ? rightmostPosition() : width() - leftOverflow; for (RenderBox* c = firstChildBox(); c; c = c->nextSiblingBox()) { int dw = c->width() + c->marginLeft() + c->marginRight(); if (dw > w) w = dw; } return w; }
size_t RenderGrid::maximumIndexInDirection(TrackSizingDirection direction) const { const Vector<GridTrackSize>& trackStyles = (direction == ForColumns) ? style()->gridColumns() : style()->gridRows(); size_t maximumIndex = trackStyles.size(); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { GridPosition position = (direction == ForColumns) ? child->style()->gridItemColumn() : child->style()->gridItemRow(); maximumIndex = std::max(maximumIndex, resolveGridPosition(position) + 1); } return maximumIndex; }
LayoutUnit RenderGrid::computePreferredTrackWidth(const Length& length, size_t trackIndex) const { if (length.isFixed()) { // Grid areas don't have borders, margins or paddings so we don't need to account for them. return length.intValue(); } if (length.isMinContent()) { LayoutUnit minContentSize = 0; // FIXME: It's inefficient to iterate over our grid items. We should be able to // get the subset of grid items in the current row / column faster. for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { size_t cellIndex = resolveGridPosition(ForColumns, child); if (cellIndex != trackIndex) continue; // FIXME: We should include the child's fixed margins like RenderFlexibleBox. minContentSize = std::max(minContentSize, child->minPreferredLogicalWidth()); } return minContentSize; } if (length.isMaxContent()) { LayoutUnit maxContentSize = 0; for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { size_t cellIndex = resolveGridPosition(ForColumns, child); if (cellIndex != trackIndex) continue; // FIXME: We should include the child's fixed margins like RenderFlexibleBox. maxContentSize = std::max(maxContentSize, child->maxPreferredLogicalWidth()); } return maxContentSize; } // FIXME: css3-sizing mentions that we should resolve "definite sizes" // (including <percentage> and calc()) but we don't do it elsewhere. return 0; }
bool RenderMathMLRoot::isValid() const { // Verify whether the list of children is valid: // <msqrt> child1 child2 ... childN </msqrt> // <mroot> base index </mroot> if (m_kind == SquareRoot) return true; ASSERT(m_kind == RootWithIndex); auto* child = firstChildBox(); if (!child) return false; child = child->nextSiblingBox(); return child && !child->nextSiblingBox(); }
int RenderView::docHeight() const { int h = lowestPosition(); // FIXME: This doesn't do any margin collapsing. // Instead of this dh computation we should keep the result // when we call RenderBlock::layout. int dh = 0; for (RenderBox* c = firstChildBox(); c; c = c->nextSiblingBox()) dh += c->height() + c->marginTop() + c->marginBottom(); if (dh > h) h = dh; return h; }
void RenderGrid::resolveContentBasedTrackSizingFunctionsForItems(TrackSizingDirection direction, Vector<GridTrack>& columnTracks, Vector<GridTrack>& rowTracks, size_t i, SizingFunction sizingFunction, AccumulatorGetter trackGetter, AccumulatorGrowFunction trackGrowthFunction) { GridTrack& track = (direction == ForColumns) ? columnTracks[i] : rowTracks[i]; for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { size_t cellIndex = resolveGridPosition(direction, child); if (cellIndex != i) continue; LayoutUnit contentSize = (this->*sizingFunction)(child, direction, columnTracks); LayoutUnit additionalBreadthSpace = contentSize - (track.*trackGetter)(); Vector<GridTrack*> tracks; tracks.append(&track); // FIXME: We should pass different values for |tracksForGrowthAboveMaxBreadth|. distributeSpaceToTracks(tracks, &tracks, trackGetter, trackGrowthFunction, additionalBreadthSpace); } }
void RenderFrameSet::positionFrames() { RenderBox* child = firstChildBox(); if (!child) return; int rows = frameSetElement().totalRows(); int cols = frameSetElement().totalCols(); int yPos = 0; int borderThickness = frameSetElement().border(); for (int r = 0; r < rows; r++) { int xPos = 0; int height = m_rows.m_sizes[r]; for (int c = 0; c < cols; c++) { child->setLocation(IntPoint(xPos, yPos)); int width = m_cols.m_sizes[c]; // has to be resized and itself resize its contents if (width != child->width() || height != child->height()) { child->setWidth(width); child->setHeight(height); #if PLATFORM(IOS) // FIXME: Is this iOS-specific? child->setNeedsLayout(MarkOnlyThis); #else child->setNeedsLayout(); #endif child->layout(); } xPos += width + borderThickness; child = child->nextSiblingBox(); if (!child) return; } yPos += height + borderThickness; } // all the remaining frames are hidden to avoid ugly spurious unflowed frames for (; child; child = child->nextSiblingBox()) { child->setWidth(0); child->setHeight(0); child->clearNeedsLayout(); } }
void RenderFrameSet::positionFrames() { RenderBox* child = firstChildBox(); if (!child) return; int rows = frameSet()->totalRows(); int cols = frameSet()->totalCols(); int yPos = 0; int borderThickness = frameSet()->border(); for (int r = 0; r < rows; r++) { int xPos = 0; int height = m_rows.m_sizes[r]; for (int c = 0; c < cols; c++) { child->setLocation(xPos, yPos); int width = m_cols.m_sizes[c]; // has to be resized and itself resize its contents if (width != child->width() || height != child->height()) { child->setWidth(width); child->setHeight(height); child->setNeedsLayout(true); child->layout(); } xPos += width + borderThickness; child = child->nextSiblingBox(); if (!child) return; } yPos += height + borderThickness; } // all the remaining frames are hidden to avoid ugly spurious unflowed frames for (; child; child = child->nextSiblingBox()) { child->setWidth(0); child->setHeight(0); child->setNeedsLayout(false); } }
void RenderGrid::layoutGridItems() { Vector<GridTrack> columnTracks(maximumIndexInDirection(ForColumns)); Vector<GridTrack> rowTracks(maximumIndexInDirection(ForRows)); computedUsedBreadthOfGridTracks(ForColumns, columnTracks, rowTracks); ASSERT(tracksAreWiderThanMinTrackBreadth(ForColumns, columnTracks)); computedUsedBreadthOfGridTracks(ForRows, columnTracks, rowTracks); ASSERT(tracksAreWiderThanMinTrackBreadth(ForRows, rowTracks)); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { LayoutPoint childPosition = findChildLogicalPosition(child, columnTracks, rowTracks); size_t columnTrack = resolveGridPosition(child->style()->gridItemColumn()); size_t rowTrack = resolveGridPosition(child->style()->gridItemRow()); // Because the grid area cannot be styled, we don't need to adjust // the grid breadth to account for 'box-sizing'. LayoutUnit oldOverrideContainingBlockContentLogicalWidth = child->hasOverrideContainingBlockLogicalWidth() ? child->overrideContainingBlockContentLogicalWidth() : LayoutUnit(); LayoutUnit oldOverrideContainingBlockContentLogicalHeight = child->hasOverrideContainingBlockLogicalHeight() ? child->overrideContainingBlockContentLogicalHeight() : LayoutUnit(); if (oldOverrideContainingBlockContentLogicalWidth != columnTracks[columnTrack].m_usedBreadth || oldOverrideContainingBlockContentLogicalHeight != rowTracks[rowTrack].m_usedBreadth) child->setNeedsLayout(true, MarkOnlyThis); child->setOverrideContainingBlockContentLogicalWidth(columnTracks[columnTrack].m_usedBreadth); child->setOverrideContainingBlockContentLogicalHeight(rowTracks[rowTrack].m_usedBreadth); // FIXME: Grid items should stretch to fill their cells. Once we // implement grid-{column,row}-align, we can also shrink to fit. For // now, just size as if we were a regular child. child->layoutIfNeeded(); // FIXME: Handle border & padding on the grid element. child->setLogicalLocation(childPosition); } for (size_t i = 0; i < rowTracks.size(); ++i) setLogicalHeight(logicalHeight() + rowTracks[i].m_usedBreadth); // FIXME: We should handle min / max logical height. setLogicalHeight(logicalHeight() + borderAndPaddingLogicalHeight()); }
void RenderSnapshottedPlugIn::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.phase == PaintPhaseForeground && plugInImageElement()->displayState() < HTMLPlugInElement::Restarting) { paintSnapshot(paintInfo, paintOffset); } PaintPhase newPhase = (paintInfo.phase == PaintPhaseChildOutlines) ? PaintPhaseOutline : paintInfo.phase; newPhase = (newPhase == PaintPhaseChildBlockBackgrounds) ? PaintPhaseChildBlockBackground : newPhase; PaintInfo paintInfoForChild(paintInfo); paintInfoForChild.phase = newPhase; paintInfoForChild.updateSubtreePaintRootForChildren(this); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { LayoutPoint childPoint = flipForWritingModeForChild(child, paintOffset); if (!child->hasSelfPaintingLayer() && !child->isFloating()) child->paint(paintInfoForChild, childPoint); } RenderEmbeddedObject::paint(paintInfo, paintOffset); }
bool RenderMultiColumnBlock::relayoutForPagination(bool, LayoutUnit, LayoutStateMaintainer& statePusher) { if (m_inBalancingPass || !requiresBalancing()) return false; m_inBalancingPass = true; // Prevent re-entering this method (and recursion into layout). bool needsRelayout; bool neededRelayout = false; bool firstPass = true; do { // Column heights may change here because of balancing. We may have to do multiple layout // passes, depending on how the contents is fitted to the changed column heights. In most // cases, laying out again twice or even just once will suffice. Sometimes we need more // passes than that, though, but the number of retries should not exceed the number of // columns, unless we have a bug. needsRelayout = false; for (RenderBox* childBox = firstChildBox(); childBox; childBox = childBox->nextSiblingBox()) if (childBox != m_flowThread && childBox->isRenderMultiColumnSet()) { RenderMultiColumnSet* multicolSet = toRenderMultiColumnSet(childBox); if (multicolSet->calculateBalancedHeight(firstPass)) { multicolSet->setChildNeedsLayout(MarkOnlyThis); needsRelayout = true; } } if (needsRelayout) { // Layout again. Column balancing resulted in a new height. neededRelayout = true; m_flowThread->setChildNeedsLayout(MarkOnlyThis); setChildNeedsLayout(MarkOnlyThis); if (firstPass) statePusher.pop(); layoutBlock(false); } firstPass = false; } while (needsRelayout); m_inBalancingPass = false; return neededRelayout; }
void Paragraph::paint(Canvas* canvas, double x, double y) { SkCanvas* skCanvas = canvas->canvas(); if (!skCanvas) return; FontCachePurgePreventer fontCachePurgePreventer; // Very simplified painting to allow painting an arbitrary (layer-less) subtree. RenderBox* box = firstChildBox(); skCanvas->translate(x, y); GraphicsContext context(skCanvas); Vector<RenderBox*> layers; LayoutRect bounds = box->absoluteBoundingBoxRect(); DCHECK(bounds.x() == 0 && bounds.y() == 0); PaintInfo paintInfo(&context, enclosingIntRect(bounds), box); box->paint(paintInfo, LayoutPoint(), layers); // Note we're ignoring any layers encountered. // TODO(abarth): Remove the concept of RenderLayers. skCanvas->translate(-x, -y); }
void RenderGrid::layoutGridItems() { Vector<GridTrack> columnTracks, rowTracks; computedUsedBreadthOfGridTracks(ForColumns, columnTracks); // FIXME: The logical width of Grid Columns from the prior step is used in // the formatting of Grid items in content-sized Grid Rows to determine // their required height. We will probably need to pass columns through. computedUsedBreadthOfGridTracks(ForRows, rowTracks); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) { LayoutPoint childPosition = findChildLogicalPosition(child, columnTracks, rowTracks); // FIXME: Grid items should stretch to fill their cells. Once we // implement grid-{column,row}-align, we can also shrink to fit. For // now, just size as if we were a regular child. child->layoutIfNeeded(); // FIXME: Handle border & padding on the grid element. child->setLogicalLocation(childPosition); } // FIXME: Handle border & padding on the grid element. for (size_t i = 0; i < rowTracks.size(); ++i) setLogicalHeight(logicalHeight() + rowTracks[i].m_usedBreadth); }
RenderBox& RenderMathMLFraction::numerator() const { ASSERT(isValid()); return *firstChildBox(); }
void RenderMultiColumnBlock::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { RenderBlockFlow::styleDidChange(diff, oldStyle); for (RenderBox* child = firstChildBox(); child; child = child->nextSiblingBox()) child->setStyle(RenderStyle::createAnonymousStyleWithDisplay(style(), BLOCK)); }
void RenderMathMLRoot::layoutBlock(bool relayoutChildren, LayoutUnit) { ASSERT(needsLayout()); if (!relayoutChildren && simplifiedLayout()) return; updateStyle(); m_radicalOperatorTop = 0; m_baseWidth = 0; if (!isValid()) { setLogicalWidth(0); setLogicalHeight(0); clearNeedsLayout(); return; } // We layout the children, determine the vertical metrics of the base and set the logical width. // Note: Per the MathML specification, the children of <msqrt> are wrapped in an inferred <mrow>, which is the desired base. LayoutUnit baseAscent, baseDescent; recomputeLogicalWidth(); if (m_kind == SquareRoot) { baseAscent = baseDescent; RenderMathMLRow::computeLineVerticalStretch(baseAscent, baseDescent); RenderMathMLRow::layoutRowItems(baseAscent, baseDescent); m_baseWidth = logicalWidth(); } else { getBase().layoutIfNeeded(); m_baseWidth = getBase().logicalWidth(); baseAscent = ascentForChild(getBase()); baseDescent = getBase().logicalHeight() - baseAscent; getIndex().layoutIfNeeded(); } // Stretch the radical operator to cover the base height. // We can then determine the metrics of the radical operator + the base. m_radicalOperator.stretchTo(style(), baseAscent + baseDescent); LayoutUnit radicalOperatorHeight = m_radicalOperator.ascent() + m_radicalOperator.descent(); LayoutUnit indexBottomRaise = m_degreeBottomRaisePercent * radicalOperatorHeight; LayoutUnit radicalAscent = baseAscent + m_verticalGap + m_ruleThickness + m_extraAscender; LayoutUnit radicalDescent = std::max<LayoutUnit>(baseDescent, radicalOperatorHeight + m_extraAscender - radicalAscent); LayoutUnit descent = radicalDescent; LayoutUnit ascent = radicalAscent; // We set the logical width. if (m_kind == SquareRoot) setLogicalWidth(m_radicalOperator.width() + m_baseWidth); else { ASSERT(m_kind == RootWithIndex); setLogicalWidth(m_kernBeforeDegree + getIndex().logicalWidth() + m_kernAfterDegree + m_radicalOperator.width() + m_baseWidth); } // For <mroot>, we update the metrics to take into account the index. LayoutUnit indexAscent, indexDescent; if (m_kind == RootWithIndex) { indexAscent = ascentForChild(getIndex()); indexDescent = getIndex().logicalHeight() - indexAscent; ascent = std::max<LayoutUnit>(radicalAscent, indexBottomRaise + indexDescent + indexAscent - descent); } // We set the final position of children. m_radicalOperatorTop = ascent - radicalAscent + m_extraAscender; LayoutUnit horizontalOffset = m_radicalOperator.width(); if (m_kind == RootWithIndex) horizontalOffset += m_kernBeforeDegree + getIndex().logicalWidth() + m_kernAfterDegree; LayoutPoint baseLocation(mirrorIfNeeded(horizontalOffset, m_baseWidth), ascent - baseAscent); if (m_kind == SquareRoot) { for (auto* child = firstChildBox(); child; child = child->nextSiblingBox()) child->setLocation(child->location() + baseLocation); } else { ASSERT(m_kind == RootWithIndex); getBase().setLocation(baseLocation); LayoutPoint indexLocation(mirrorIfNeeded(m_kernBeforeDegree, getIndex()), ascent + descent - indexBottomRaise - indexDescent - indexAscent); getIndex().setLocation(indexLocation); } setLogicalHeight(ascent + descent); clearNeedsLayout(); }
RenderBox& RenderMathMLRoot::getBase() const { ASSERT(isValid()); ASSERT(m_kind == RootWithIndex); return *firstChildBox(); }
RenderBox& RenderMathMLRoot::getIndex() const { ASSERT(isValid()); ASSERT(m_kind == RootWithIndex); return *firstChildBox()->nextSiblingBox(); }
void RenderFrameSet::positionFramesWithFlattening() { RenderBox* child = firstChildBox(); if (!child) return; int rows = frameSetElement().totalRows(); int cols = frameSetElement().totalCols(); int borderThickness = frameSetElement().border(); bool repaintNeeded = false; // calculate frameset height based on actual content height to eliminate scrolling bool out = false; for (int r = 0; r < rows && !out; r++) { int extra = 0; int height = m_rows.m_sizes[r]; for (int c = 0; c < cols; c++) { IntRect oldFrameRect = pixelSnappedIntRect(child->frameRect()); int width = m_cols.m_sizes[c]; bool fixedWidth = frameSetElement().colLengths() && frameSetElement().colLengths()[c].isFixed(); bool fixedHeight = frameSetElement().rowLengths() && frameSetElement().rowLengths()[r].isFixed(); // has to be resized and itself resize its contents if (!fixedWidth) child->setWidth(width ? width + extra / (cols - c) : 0); else child->setWidth(width); child->setHeight(height); child->setNeedsLayout(); if (child->isFrameSet()) toRenderFrameSet(child)->layout(); else toRenderFrame(child)->layoutWithFlattening(fixedWidth, fixedHeight); if (child->height() > m_rows.m_sizes[r]) m_rows.m_sizes[r] = child->height(); if (child->width() > m_cols.m_sizes[c]) m_cols.m_sizes[c] = child->width(); if (child->frameRect() != oldFrameRect) repaintNeeded = true; // difference between calculated frame width and the width it actually decides to have extra += width - m_cols.m_sizes[c]; child = child->nextSiblingBox(); if (!child) { out = true; break; } } } int xPos = 0; int yPos = 0; out = false; child = firstChildBox(); for (int r = 0; r < rows && !out; r++) { xPos = 0; for (int c = 0; c < cols; c++) { // ensure the rows and columns are filled IntRect oldRect = pixelSnappedIntRect(child->frameRect()); child->setLocation(IntPoint(xPos, yPos)); child->setHeight(m_rows.m_sizes[r]); child->setWidth(m_cols.m_sizes[c]); if (child->frameRect() != oldRect) { repaintNeeded = true; // update to final size child->setNeedsLayout(); if (child->isFrameSet()) toRenderFrameSet(child)->layout(); else toRenderFrame(child)->layoutWithFlattening(true, true); } xPos += m_cols.m_sizes[c] + borderThickness; child = child->nextSiblingBox(); if (!child) { out = true; break; } } yPos += m_rows.m_sizes[r] + borderThickness; } setWidth(xPos - borderThickness); setHeight(yPos - borderThickness); if (repaintNeeded) repaint(); // all the remaining frames are hidden to avoid ugly spurious unflowed frames for (; child; child = child->nextSiblingBox()) { child->setWidth(0); child->setHeight(0); child->clearNeedsLayout(); } }
RenderBox& RenderMathMLFraction::denominator() const { ASSERT(isValid()); return *firstChildBox()->nextSiblingBox(); }