All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.openhtmltopdf.render.BlockBox Maven / Gradle / Ivy

Go to download

Open HTML to PDF is a CSS 2.1 renderer written in Java. This artifact contains the core rendering and layout code.

The newest version!
/*
 * {{{ header & license
 * Copyright (c) 2004, 2005 Joshua Marinacci
 * Copyright (c) 2006, 2007 Wisconsin Courts System
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * }}}
 */
package com.openhtmltopdf.render;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.w3c.dom.Element;

import com.openhtmltopdf.css.constants.CSSName;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.newmatch.CascadedStyle;
import com.openhtmltopdf.css.parser.FSRGBColor;
import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.css.style.CssContext;
import com.openhtmltopdf.css.style.FSDerivedValue;
import com.openhtmltopdf.css.style.derived.BorderPropertySet;
import com.openhtmltopdf.css.style.derived.LengthValue;
import com.openhtmltopdf.css.style.derived.RectPropertySet;
import com.openhtmltopdf.extend.FSImage;
import com.openhtmltopdf.extend.ReplacedElement;
import com.openhtmltopdf.layout.BlockBoxing;
import com.openhtmltopdf.layout.BlockFormattingContext;
import com.openhtmltopdf.layout.BoxBuilder;
import com.openhtmltopdf.layout.BreakAtLineContext;
import com.openhtmltopdf.layout.CounterFunction;
import com.openhtmltopdf.layout.FloatManager;
import com.openhtmltopdf.layout.InlineBoxing;
import com.openhtmltopdf.layout.LayoutContext;
import com.openhtmltopdf.layout.PaintingInfo;
import com.openhtmltopdf.layout.PersistentBFC;
import com.openhtmltopdf.layout.Styleable;
import com.openhtmltopdf.newtable.TableRowBox;
import com.openhtmltopdf.util.ThreadCtx;

/**
 * A block box as defined in the CSS spec.  It also provides a base class for
 * other kinds of block content (for example table rows or cells).
 * See {@link ContentType}
 */
public class BlockBox extends Box {

    public static final int POSITION_VERTICALLY = 1;
    public static final int POSITION_HORIZONTALLY = 2;
    public static final int POSITION_BOTH = POSITION_VERTICALLY | POSITION_HORIZONTALLY;

    /**
     * What type of direct child content this block box contains.
     * 

* NOTE: A {@link BlockBox} can only contain inline or block content (not both) as direct children. * If this constraint is not met by the original document, the {@link BoxBuilder} * will insert {@link AnonymousBlockBox} with inline content. */ public static enum ContentType { /** * The box builder has not yet run to * create our child boxes. The box builder can be run * with {@link BlockBox#ensureChildren(LayoutContext)}. */ UNKNOWN, /** * This block box contains inline content in the {@link BlockBox#getInlineContent()} * property. If it has also been laid out it will contain * children in {@link Box#getChildren()} and associated methods. * Children will be only {@link LineBox} objects. */ INLINE, /** * This block box's direct children consist only of * {@link BlockBox} and subclassed objects. * The method {@link BlockBox#setInlineContent(List)} must not be used * with block content. */ BLOCK, /** * This block box is empty but may still have border, etc. */ EMPTY; } protected static final int NO_BASELINE = Integer.MIN_VALUE; private MarkerData _markerData; private int _listCounter; private PersistentBFC _persistentBFC; private Box _staticEquivalent; private boolean _needPageClear; private ReplacedElement _replacedElement; private ContentType _childrenContentType = ContentType.UNKNOWN; private List _inlineContent; private boolean _topMarginCalculated; private boolean _bottomMarginCalculated; private MarginCollapseResult _pendingCollapseCalculation; private int _minWidth; private int _maxWidth; private boolean _minMaxCalculated; private boolean _dimensionsCalculated; private boolean _needShrinkToFitCalculatation; private CascadedStyle _firstLineStyle; private CascadedStyle _firstLetterStyle; private FloatedBoxData _floatedBoxData; private int _childrenHeight; private boolean _fromCaptionedTable; private boolean _isReplaced; public BlockBox() { super(); } @Override public void setElement(Element element) { super.setElement(element); _isReplaced = ThreadCtx.get().sharedContext().getReplacedElementFactory().isReplacedElement(element); } public BlockBox copyOf() { BlockBox result = new BlockBox(); result.setStyle(getStyle()); result.setElement(getElement()); return result; } protected String getExtraBoxDescription() { return ""; } @Override public String toString() { StringBuilder result = new StringBuilder(); String className = getClass().getName(); result.append(className.substring(className.lastIndexOf('.') + 1)); result.append(": "); if (getElement() != null && ! isAnonymous()) { result.append("<"); result.append(getElement().getNodeName()); result.append("> "); } if (isAnonymous()) { result.append("(anonymous) "); } if (getPseudoElementOrClass() != null) { result.append(':'); result.append(getPseudoElementOrClass()); result.append(' '); } result.append('('); result.append(getStyle().getIdent(CSSName.DISPLAY).toString()); result.append(") "); if (getStyle().isRunning()) { result.append("(running) "); } result.append('('); switch (getChildrenContentType()) { case BLOCK: result.append('B'); break; case INLINE: result.append('I'); break; case EMPTY: result.append('E'); break; case UNKNOWN: result.append('U'); break; default: result.append('U'); break; } result.append(") "); result.append(getExtraBoxDescription()); appendPositioningInfo(result); result.append("(" + getAbsX() + "," + getAbsY() + ")->(" + getWidth() + " x " + getHeight() + ")"); return result.toString(); } protected void appendPositioningInfo(StringBuilder result) { if (getStyle().isRelative()) { result.append("(relative) "); } if (getStyle().isFixed()) { result.append("(fixed) "); } if (getStyle().isAbsolute()) { result.append("(absolute) "); } if (getStyle().isFloated()) { result.append("(floated) "); } } @Override public String dump(LayoutContext c, String indent, int which) { StringBuilder result = new StringBuilder(indent); ensureChildren(c); result.append(this); result.append(getMargin(c).toString(" effMargin=")); result.append(getStyleMargin(c).toString(" styleMargin=")); if (getChildrenContentType() != ContentType.EMPTY) { result.append('\n'); } switch (getChildrenContentType()) { case BLOCK: dumpBoxes(c, indent, getChildren(), which, result); break; case INLINE: if (which == Box.DUMP_RENDER) { dumpBoxes(c, indent, getChildren(), which, result); } else { for (Iterator i = getInlineContent().iterator(); i.hasNext();) { Styleable styleable = i.next(); if (styleable instanceof BlockBox) { BlockBox b = (BlockBox) styleable; result.append(b.dump(c, indent + " ", which)); if (result.charAt(result.length() - 1) == '\n') { result.deleteCharAt(result.length() - 1); } } else { result.append(indent + " "); result.append(styleable.toString()); } if (i.hasNext()) { result.append('\n'); } } } break; case EMPTY: break; case UNKNOWN: break; default: break; } return result.toString(); } public boolean isListItem() { return getStyle().isListItem(); } public void paintListMarker(RenderingContext c) { if (! getStyle().isVisible(c, this)) { return; } if (isListItem()) { ListItemPainter.paint(c, this); } } @Override public Rectangle getPaintingClipEdge(CssContext cssCtx) { Rectangle result = super.getPaintingClipEdge(cssCtx); // HACK Don't know how wide the list marker is (or even where it is) // so extend the bounding box all the way over to the left edge of // the canvas if (getStyle().isListItem()) { int delta = result.x; result.x = 0; result.width += delta; } return result; } public boolean isInline() { Box parent = getParent(); return parent instanceof LineBox || parent instanceof InlineLayoutBox; } public LineBox getLineBox() { if (! isInline()) { return null; } else { return (LineBox) findAncestor(bx -> bx instanceof LineBox); } } public void paintDebugOutline(RenderingContext c) { c.getOutputDevice().drawDebugOutline(c, this, FSRGBColor.RED); } public MarkerData getMarkerData() { return _markerData; } public void setMarkerData(MarkerData markerData) { _markerData = markerData; } public void createMarkerData(LayoutContext c) { if (getMarkerData() != null) { return; } StrutMetrics strutMetrics = InlineBoxing.createDefaultStrutMetrics(c, this); boolean imageMarker = false; MarkerData result = new MarkerData(); result.setStructMetrics(strutMetrics); CalculatedStyle style = getStyle(); IdentValue listStyle = style.getIdent(CSSName.LIST_STYLE_TYPE); String image = style.getStringProperty(CSSName.LIST_STYLE_IMAGE); if (! image.equals("none")) { result.setImageMarker(makeImageMarker(c, strutMetrics, image)); imageMarker = result.getImageMarker() != null; } if (listStyle != IdentValue.NONE && ! imageMarker) { if (listStyle == IdentValue.CIRCLE || listStyle == IdentValue.SQUARE || listStyle == IdentValue.DISC) { result.setGlyphMarker(makeGlyphMarker(strutMetrics)); } else { result.setTextMarker(makeTextMarker(c, listStyle)); } } setMarkerData(result); } private MarkerData.GlyphMarker makeGlyphMarker(StrutMetrics strutMetrics) { int diameter = (int) ((strutMetrics.getAscent() + strutMetrics.getDescent()) / 3); MarkerData.GlyphMarker result = new MarkerData.GlyphMarker(); result.setDiameter(diameter); result.setLayoutWidth(diameter * 3); return result; } private MarkerData.ImageMarker makeImageMarker( LayoutContext c, StrutMetrics structMetrics, String image) { FSImage img = null; if (! image.equals("none")) { img = c.getUac().getImageResource(image).getImage(); if (img != null) { StrutMetrics strutMetrics = structMetrics; if (img.getHeight() > strutMetrics.getAscent()) { img.scale(-1, (int) strutMetrics.getAscent()); } MarkerData.ImageMarker result = new MarkerData.ImageMarker(); result.setImage(img); result.setLayoutWidth(img.getWidth() * 2); return result; } } return null; } private MarkerData.TextMarker makeTextMarker(LayoutContext c, IdentValue listStyle) { String text; int listCounter = getListCounter(); text = CounterFunction.createCounterText(listStyle, listCounter); IdentValue listDirection = getParent().getStyle().getDirection(); if (listDirection == IdentValue.RTL) { text = " .".concat(text); } else { assert listDirection == IdentValue.LTR || listDirection == IdentValue.AUTO; text = text.concat(". "); } int w = c.getTextRenderer().getWidth( c.getFontContext(), getStyle().getFSFont(c), text); MarkerData.TextMarker result = new MarkerData.TextMarker(); result.setLayoutWidth(w); result.setText(text); return result; } public int getListCounter() { return _listCounter; } public void setListCounter(int listCounter) { _listCounter = listCounter; } public PersistentBFC getPersistentBFC() { return _persistentBFC; } public void setPersistentBFC(PersistentBFC persistentBFC) { _persistentBFC = persistentBFC; } public Box getStaticEquivalent() { return _staticEquivalent; } public void setStaticEquivalent(Box staticEquivalent) { _staticEquivalent = staticEquivalent; } public boolean shouldBeReplaced() { return _isReplaced; } public boolean isReplaced() { return _replacedElement != null; } @Override public void calcCanvasLocation() { if (isFloated()) { FloatManager manager = _floatedBoxData.getManager(); if (manager != null) { Point offset = manager.getOffset(this); setAbsX(manager.getMaster().getAbsX() + getX() - offset.x); setAbsY(manager.getMaster().getAbsY() + getY() - offset.y); } } LineBox lineBox = getLineBox(); if (lineBox == null) { Box parent = getParent(); if (parent != null) { setAbsX(parent.getAbsX() + parent.getTx() + getX()); setAbsY(parent.getAbsY() + parent.getTy() + getY()); } else if (isStyled() && getStyle().isAbsFixedOrInlineBlockEquiv()) { Box cb = getContainingBlock(); if (cb != null) { setAbsX(cb.getAbsX() + getX()); setAbsY(cb.getAbsY() + getY()); } } } else { setAbsX(lineBox.getAbsX() + getX()); setAbsY(lineBox.getAbsY() + getY()); } if (isReplaced()) { Point location = getReplacedElement().getLocation(); if (location.x != getAbsX() || location.y != getAbsY()) { getReplacedElement().setLocation(getAbsX(), getAbsY()); } } } public void calcInitialFloatedCanvasLocation(LayoutContext c) { Point offset = c.getBlockFormattingContext().getOffset(); FloatManager manager = c.getBlockFormattingContext().getFloatManager(); setAbsX(manager.getMaster().getAbsX() + getX() - offset.x); setAbsY(manager.getMaster().getAbsY() + getY() - offset.y); } @Override public void calcChildLocations() { super.calcChildLocations(); if (_persistentBFC != null) { _persistentBFC.getFloatManager().calcFloatLocations(); } } public boolean isNeedPageClear() { return _needPageClear; } public void setNeedPageClear(boolean needPageClear) { _needPageClear = needPageClear; } private void alignToStaticEquivalent() { if (_staticEquivalent.getAbsY() != getAbsY()) { setY(_staticEquivalent.getAbsY() - getAbsY()); setAbsY(_staticEquivalent.getAbsY()); } } public void positionAbsolute(CssContext cssCtx, int direction) { CalculatedStyle style = getStyle(); Rectangle boundingBox = null; int cbContentHeight = getContainingBlock().getContentAreaEdge(0, 0, cssCtx).height; if (getContainingBlock() instanceof BlockBox) { boundingBox = getContainingBlock().getPaddingEdge(0, 0, cssCtx); } else { boundingBox = getContainingBlock().getContentAreaEdge(0, 0, cssCtx); } if ((direction & POSITION_HORIZONTALLY) != 0) { setX(0); if (!style.isIdent(CSSName.LEFT, IdentValue.AUTO)) { setX((int) style.getFloatPropertyProportionalWidth(CSSName.LEFT, getContainingBlock().getContentWidth(), cssCtx)); } else if (!style.isIdent(CSSName.RIGHT, IdentValue.AUTO)) { setX(boundingBox.width - (int) style.getFloatPropertyProportionalWidth(CSSName.RIGHT, getContainingBlock().getContentWidth(), cssCtx) - getWidth()); } setX(getX() + boundingBox.x); } if ((direction & POSITION_VERTICALLY) != 0) { setY(0); if (!style.isIdent(CSSName.TOP, IdentValue.AUTO)) { setY((int) style.getFloatPropertyProportionalHeight(CSSName.TOP, cbContentHeight, cssCtx)); } else if (!style.isIdent(CSSName.BOTTOM, IdentValue.AUTO)) { setY(boundingBox.height - (int) style.getFloatPropertyProportionalWidth(CSSName.BOTTOM, cbContentHeight, cssCtx) - getHeight()); } // Can't do this before now because our containing block // must be completed layed out int pinnedHeight = calcPinnedHeight(cssCtx); if (pinnedHeight != -1 && getCSSHeight(cssCtx) == -1) { setHeight(pinnedHeight); applyCSSMinMaxHeight(cssCtx); } setY(getY() + boundingBox.y); } calcCanvasLocation(); if ((direction & POSITION_VERTICALLY) != 0 && getStyle().isTopAuto() && getStyle().isBottomAuto()) { alignToStaticEquivalent(); } calcChildLocations(); } /** * Using the css: * * -fs-page-break-min-height: 5cm; * * on a block element you can force a pagebreak before this block, if not * enough space (e.g. 5cm in this case) is remaining on the current page for the block. * * @return true if a pagebreak is needed before this block because * there is not enough space left on the current page. */ public boolean isPageBreakNeededBecauseOfMinHeight(LayoutContext context){ float minHeight = getStyle().getFSPageBreakMinHeight(context); PageBox page = context.getRootLayer().getFirstPage(context, this); return page != null && getAbsY() + minHeight > page.getBottom(context); } public void positionAbsoluteOnPage(LayoutContext c) { if (c.isPrint() && (getStyle().isForcePageBreakBefore() || isNeedPageClear() || isPageBreakNeededBecauseOfMinHeight(c))) { forcePageBreakBefore(c, getStyle().getIdent(CSSName.PAGE_BREAK_BEFORE), false); calcCanvasLocation(); calcChildLocations(); setNeedPageClear(false); } } public ReplacedElement getReplacedElement() { return _replacedElement; } public void setReplacedElement(ReplacedElement replacedElement) { _replacedElement = replacedElement; } @Override public void reset(LayoutContext c) { super.reset(c); setTopMarginCalculated(false); setBottomMarginCalculated(false); setDimensionsCalculated(false); setMinMaxCalculated(false); setChildrenHeight(0); if (isReplaced()) { getReplacedElement().detach(c); setReplacedElement(null); } if (getChildrenContentType() == ContentType.INLINE) { removeAllChildren(); } if (isFloated()) { _floatedBoxData.getManager().removeFloat(this); _floatedBoxData.getDrawingLayer().removeFloat(this); } if (getStyle().isRunning()) { c.getRootLayer().removeRunningBlock(this); } } private int calcPinnedContentWidth(CssContext c) { if (! getStyle().isIdent(CSSName.LEFT, IdentValue.AUTO) && ! getStyle().isIdent(CSSName.RIGHT, IdentValue.AUTO)) { Rectangle paddingEdge = getContainingBlock().getPaddingEdge(0, 0, c); int left = (int) getStyle().getFloatPropertyProportionalTo( CSSName.LEFT, paddingEdge.width, c); int right = (int) getStyle().getFloatPropertyProportionalTo( CSSName.RIGHT, paddingEdge.width, c); int result = paddingEdge.width - left - right - getLeftMBP() - getRightMBP(); return result < 0 ? 0 : result; } return -1; } private int calcPinnedHeight(CssContext c) { if (! getStyle().isIdent(CSSName.TOP, IdentValue.AUTO) && ! getStyle().isIdent(CSSName.BOTTOM, IdentValue.AUTO)) { Rectangle paddingEdge = getContainingBlock().getPaddingEdge(0, 0, c); int top = (int) getStyle().getFloatPropertyProportionalTo( CSSName.TOP, paddingEdge.height, c); int bottom = (int) getStyle().getFloatPropertyProportionalTo( CSSName.BOTTOM, paddingEdge.height, c); int result = paddingEdge.height - top - bottom; return result < 0 ? 0 : result; } return -1; } protected void resolveAutoMargins( LayoutContext c, int cssWidth, RectPropertySet padding, BorderPropertySet border) { int withoutMargins = (int) border.left() + (int) padding.left() + cssWidth + (int) padding.right() + (int) border.right(); if (withoutMargins < getContainingBlockWidth()) { int available = getContainingBlockWidth() - withoutMargins; boolean autoLeft = getStyle().isAutoLeftMargin(); boolean autoRight = getStyle().isAutoRightMargin(); if (autoLeft && autoRight) { setMarginLeft(c, available / 2); setMarginRight(c, available / 2); } else if (autoLeft) { setMarginLeft(c, available); } else if (autoRight) { setMarginRight(c, available); } } } private int calcEffPageRelativeWidth(LayoutContext c) { int totalLeftMBP = 0; int totalRightMBP = 0; boolean usePageRelativeWidth = true; Box current = this; while (true) { CalculatedStyle style = current.getStyle(); if (style.isAutoWidth() && ! style.isCanBeShrunkToFit()) { totalLeftMBP += current.getLeftMBP(); totalRightMBP += current.getRightMBP(); } else { usePageRelativeWidth = false; break; } if (current.getContainingBlock().isInitialContainingBlock()) { break; } else { current = current.getContainingBlock(); } } if (usePageRelativeWidth) { PageBox currentPage = c.getRootLayer().getFirstPage(c, this); return currentPage.getContentWidth(c) - totalLeftMBP - totalRightMBP; } else { return getContainingBlockWidth() - getLeftMBP() - getRightMBP(); } } /** * Creates the replaced element as required. This method should be idempotent. */ private void createReplaced(LayoutContext c) { ReplacedElement re = getReplacedElement(); if (re == null) { int cssWidth = getCSSWidth(c); int cssHeight = getCSSHeight(c); // Since the interface doesn't allow us to pass min-width/height // we implement it here. int minWidth = getCSSMinWidth(c); int minHeight = getCSSMinHeight(c); if (minWidth > cssWidth && minWidth > 0) { cssWidth = minWidth; } if (minHeight > cssHeight && minHeight > 0) { cssHeight = minHeight; } re = c.getReplacedElementFactory().createReplacedElement( c, this, c.getUac(), cssWidth, cssHeight); if (re != null) { setReplacedElement(re); sizeReplacedElement(c, re); } } } /** * Size a replaced element taking into account size properties including min/max, * border-box/content-box and the natural size/aspect ratio of the replaced object. * * This method may be called multiple times so must be idempotent. */ private void sizeReplacedElement(LayoutContext c, ReplacedElement re) { int cssWidth = getCSSWidth(c); int cssHeight = getCSSHeight(c); boolean haveExactDims = cssWidth >= 0 && cssHeight >= 0; boolean usedMinWidth = false; boolean usedMinHeight = false; boolean usedMaxWidth = false; boolean usedMaxHeight = false; int intrinsicWidth = re.getIntrinsicWidth(); int intrinsicHeight = re.getIntrinsicHeight(); int minWidth = getCSSMinWidth(c); int minHeight = getCSSMinHeight(c); // Clamp w to max-width if required. if (!getStyle().isMaxWidthNone() && (intrinsicWidth > getCSSMaxWidth(c) || cssWidth > getCSSMaxWidth(c))) { cssWidth = getCSSMaxWidth(c); usedMaxWidth = true; } // Clamp w to min-width if required. if (cssWidth >= 0 && minWidth > 0 && cssWidth < minWidth) { cssWidth = minWidth; usedMinWidth = true; } // Clamp h to max-height if required. if (!getStyle().isMaxHeightNone() && (intrinsicHeight > getCSSMaxHeight(c) || cssHeight > getCSSMaxHeight(c))) { cssHeight = getCSSMaxHeight(c); usedMaxHeight = true; } // Clamp h to min-height if required. if (cssHeight >= 0 && minHeight > 0 && cssHeight < minHeight) { cssHeight = minHeight; usedMinHeight = true; } if (getStyle().isBorderBox()) { BorderPropertySet border = getBorder(c); RectPropertySet padding = getPadding(c); cssWidth = cssWidth < 0 ? cssWidth : (int) Math.max(0, cssWidth - border.width() - padding.width()); cssHeight = cssHeight < 0 ? cssHeight : (int) Math.max(0, cssHeight - border.height() - padding.height()); } int nw; int nh; boolean useExact = (haveExactDims && !usedMaxHeight && !usedMaxWidth && !usedMinWidth && !usedMinHeight); if (cssWidth > 0 && cssHeight > 0) { if (useExact) { // We can warp the aspect ratio if we have explicit width and height values // and the max/min values have not taken precedence. nw = cssWidth; nh = cssHeight; } else if (intrinsicWidth > cssWidth || intrinsicHeight > cssHeight) { // Too large, so reduce respecting the aspect ratio. double rw = (double) intrinsicWidth / (double) cssWidth; double rh = (double) intrinsicHeight / (double) cssHeight; if (rw > rh) { nw = cssWidth; nh = (int) (intrinsicHeight / rw); } else { nw = (int) (intrinsicWidth / rh); nh = cssHeight; } } else { // Too small. double rw = (double) intrinsicWidth / (double) cssWidth; double rh = (double) intrinsicHeight / (double) cssHeight; if (rw > rh) { nw = cssWidth; nh = ((int) (intrinsicHeight / rw)); } else { nw = ((int) (intrinsicWidth / rh)); nh = cssHeight; } } } else if (cssWidth > 0) { // Explicit min/max/width with auto height so keep aspect ratio. nw = cssWidth; nh = ((int) (((double) cssWidth / (double) intrinsicWidth) * intrinsicHeight)); } else if (cssHeight > 0) { // Explicit min/max/height with auto width. nh = cssHeight; nw = ((int) (((double) cssHeight / (double) intrinsicHeight) * intrinsicWidth)); } else if (cssWidth == 0 || cssHeight == 0) { // Empty. nw = cssWidth; nh = cssHeight; } else { // Auto width and height so use the natural dimensions of the replaced object. nw = intrinsicWidth; nh = intrinsicHeight; } setContentWidth(nw); setHeight(nh); } public void calcDimensions(LayoutContext c) { calcDimensions(c, getCSSWidth(c)); } protected void calcDimensions(LayoutContext c, int cssWidth) { if (! isDimensionsCalculated()) { CalculatedStyle style = getStyle(); RectPropertySet padding = getPadding(c); BorderPropertySet border = getBorder(c); if (cssWidth != -1 && !isAnonymous() && (getStyle().isIdent(CSSName.MARGIN_LEFT, IdentValue.AUTO) || getStyle().isIdent(CSSName.MARGIN_RIGHT, IdentValue.AUTO)) && getStyle().isNeedAutoMarginResolution()) { resolveAutoMargins(c, cssWidth, padding, border); } recalcMargin(c); RectPropertySet margin = getMargin(c); // CLEAN: cast to int setLeftMBP((int) margin.left() + (int) border.left() + (int) padding.left()); setRightMBP((int) padding.right() + (int) border.right() + (int) margin.right()); createReplaced(c); if (isReplaced()) { setDimensionsCalculated(true); return; } if (c.isPrint() && getStyle().isDynamicAutoWidth()) { setContentWidth(calcEffPageRelativeWidth(c)); } else { setContentWidth((getContainingBlockWidth() - getLeftMBP() - getRightMBP())); } setHeight(0); if (! isAnonymous() || (isFromCaptionedTable() && isFloated())) { int pinnedContentWidth = -1; if (cssWidth != -1) { if (style.isBorderBox()) { setBorderBoxWidth(c, cssWidth); } else { setContentWidth(cssWidth); } } else if (getStyle().isAbsolute() || getStyle().isFixed()) { pinnedContentWidth = calcPinnedContentWidth(c); if (pinnedContentWidth != -1) { setContentWidth(pinnedContentWidth); } } int cssHeight = getCSSHeight(c); if (cssHeight != -1) { if (style.isBorderBox()) { setBorderBoxHeight(c, cssHeight); } else { setHeight(cssHeight); } } ReplacedElement re = getReplacedElement(); if (re != null) { } else if (cssWidth == -1 && pinnedContentWidth == -1 && style.isCanBeShrunkToFit()) { setNeedShrinkToFitCalculatation(true); } if (! isReplaced()) { applyCSSMinMaxWidth(c); } } setDimensionsCalculated(true); } } private void calcClearance(LayoutContext c) { if (getStyle().isCleared() && ! getStyle().isFloated()) { c.translate(0, -getY()); c.getBlockFormattingContext().clear(c, this); c.translate(0, getY()); calcCanvasLocation(); } } private void calcExtraPageClearance(LayoutContext c) { if (c.isPageBreaksAllowed() && c.getExtraSpaceTop() > 0 && (getStyle().isSpecifiedAsBlock() || getStyle().isListItem())) { PageBox first = c.getRootLayer().getFirstPage(c, this); if (first != null && first.getTop() + c.getExtraSpaceTop() > getAbsY()) { int diff = first.getTop() + c.getExtraSpaceTop() - getAbsY(); setY(getY() + diff); c.translate(0, diff); calcCanvasLocation(); } } } protected void addBoxID(LayoutContext c) { if (! isAnonymous()) { String name = c.getNamespaceHandler().getAnchorName(getElement()); if (name != null) { c.addBoxId(name, this); } String id = c.getNamespaceHandler().getID(getElement()); if (id != null) { c.addBoxId(id, this); } } } public void layout(LayoutContext c) { layout(c, 0); } public void layout(LayoutContext c, int contentStart) { CalculatedStyle style = getStyle(); boolean pushedLayer = checkPushLayer(c, style); calcClearance(c); checkPushBfc(c); addBoxID(c); if (c.isPrint() && getStyle().isIdent(CSSName.FS_PAGE_SEQUENCE, IdentValue.START)) { c.getRootLayer().addPageSequence(this); } createReplaced(c); calcDimensions(c); calcShrinkToFitWidthIfNeeded(c); collapseMargins(c); calcExtraPageClearance(c); if (c.isPrint()) { PageBox firstPage = c.getRootLayer().getFirstPage(c, this); if (firstPage != null && firstPage.getTop() == getAbsY() - getPageClearance()) { resetTopMargin(c); } } BorderPropertySet border = getBorder(c); RectPropertySet margin = getMargin(c); RectPropertySet padding = getPadding(c); // save height in case fixed height int originalHeight = getHeight(); if (! isReplaced()) { setHeight(0); } boolean didSetMarkerData = false; if (getStyle().isListItem()) { createMarkerData(c); c.setCurrentMarkerData(getMarkerData()); didSetMarkerData = true; } // do children's layout int tx = (int) margin.left() + (int) border.left() + (int) padding.left(); int ty = (int) margin.top() + (int) border.top() + (int) padding.top(); setTx(tx); setTy(ty); c.translate(getTx(), getTy()); if (! isReplaced()) { layoutChildren(c, contentStart); } else { setState(Box.DONE); } c.translate(-getTx(), -getTy()); setChildrenHeight(getHeight()); if (! isReplaced()) { if (! isAutoHeight()) { int delta = originalHeight - getHeight(); if (delta > 0 || isAllowHeightToShrink()) { setHeight(originalHeight); } } applyCSSMinMaxHeight(c); } if (isRoot() || getStyle().establishesBFC()) { if (getStyle().isAutoHeight()) { int delta = c.getBlockFormattingContext().getFloatManager().getClearDelta( c, getTy() + getHeight()); if (delta > 0) { setHeight(getHeight() + delta); setChildrenHeight(getChildrenHeight() + delta); } } } if (didSetMarkerData) { c.setCurrentMarkerData(null); } calcLayoutHeight(c, border, margin, padding); checkPopBfc(c); if (pushedLayer) { c.popLayer(); } } protected boolean checkPushLayer(LayoutContext c, CalculatedStyle style) { if (isRoot()) { c.pushLayer(this); if (c.isPrint()) { if (!style.isIdent(CSSName.PAGE, IdentValue.AUTO)) { c.setPageName(style.getStringProperty(CSSName.PAGE)); } c.getRootLayer().addPage(c); } return true; } else if (style.requiresLayer() && this.getLayer() == null) { c.pushLayer(this); return true; } else if (style.requiresLayer()) { // FIXME: HACK. Some boxes can be layed out many times (to satisfy page constraints for example). // If this happens we just mark our old layer for deletion and create a new layer. // Not sure this is right, but doesn't break any correct tests. // // NOTE: This only happens if someone has called layout multiple times // without calling reset beforehand. this.getLayer().setForDeletion(true); c.pushLayer(this); return true; } return false; } /** * Checks if this box established a block formatting context and if so * removes the last bfc from the stack. * See also {@link #checkPushBfc(LayoutContext)} */ protected void checkPopBfc(LayoutContext c) { if (isRoot() || getStyle().establishesBFC()) { c.popBFC(); } } /** * Checks if this box establishes a block formatting context and if * so creates one and pushes it to the stack of bfcs. * See also {@link #checkPopBfc(LayoutContext)} */ protected void checkPushBfc(LayoutContext c) { if (isRoot() || getStyle().establishesBFC() || isMarginAreaRoot()) { BlockFormattingContext bfc = new BlockFormattingContext(this, c); c.pushBFC(bfc); } } protected boolean isAllowHeightToShrink() { return true; } protected int getPageClearance() { return 0; } /** * Oh oh! Up to this method height is used to track content height. After this method it is used * to track total layout height! */ protected void calcLayoutHeight( LayoutContext c, BorderPropertySet border, RectPropertySet margin, RectPropertySet padding) { setHeight(getHeight() + ((int) margin.top() + (int) border.top() + (int) padding.top() + (int) padding.bottom() + (int) border.bottom() + (int) margin.bottom())); setChildrenHeight(getChildrenHeight() + ((int) margin.top() + (int) border.top() + (int) padding.top() + (int) padding.bottom() + (int) border.bottom() + (int) margin.bottom())); } private void calcShrinkToFitWidthIfNeeded(LayoutContext c) { if (isNeedShrinkToFitCalculatation()) { setContentWidth(calcShrinkToFitWidth(c) - getLeftMBP() - getRightMBP()); applyCSSMinMaxWidth(c); setNeedShrinkToFitCalculatation(false); } } private void applyCSSMinMaxWidth(CssContext c) { int w = getStyle().isBorderBox() ? getBorderBoxWidth(c) : getContentWidth(); if (! getStyle().isMaxWidthNone()) { int cssMaxWidth = getCSSMaxWidth(c); if (w > cssMaxWidth) { if (getStyle().isBorderBox()) { setBorderBoxWidth(c, cssMaxWidth); } else { setContentWidth(cssMaxWidth); } } } int cssMinWidth = getCSSMinWidth(c); if (cssMinWidth > 0 && w < cssMinWidth) { if (getStyle().isBorderBox()) { setBorderBoxWidth(c, cssMinWidth); } else { setContentWidth(cssMinWidth); } } } private void applyCSSMinMaxHeight(CssContext c) { int currentHeight = getStyle().isBorderBox() ? getBorderBoxHeight(c) : getHeight(); if (! getStyle().isMaxHeightNone()) { int cssMaxHeight = getCSSMaxHeight(c); if (currentHeight > cssMaxHeight) { if (getStyle().isBorderBox()) { setBorderBoxHeight(c, cssMaxHeight); } else { setHeight(cssMaxHeight); } } } int cssMinHeight = getCSSMinHeight(c); if (cssMinHeight > 0 && currentHeight < cssMinHeight) { if (getStyle().isBorderBox()) { setBorderBoxHeight(c, cssMinHeight); } else { setHeight(cssMinHeight); } } } public void ensureChildren(LayoutContext c) { if (getChildrenContentType() == ContentType.UNKNOWN) { BoxBuilder.createChildren(c, this); } } protected void layoutChildren(LayoutContext c, int contentStart) { setState(Box.CHILDREN_FLUX); ensureChildren(c); if (getFirstLetterStyle() != null) { c.setFirstLettersTracker( c.getFirstLettersTracker().withStyle(getFirstLetterStyle())); } if (getFirstLineStyle() != null) { c.setFirstLinesTracker( c.getFirstLinesTracker().withStyle(getFirstLineStyle())); } switch (getChildrenContentType()) { case INLINE: layoutInlineChildren(c, contentStart, calcInitialBreakAtLine(c), true); break; case BLOCK: BlockBoxing.layoutContent(c, this, contentStart); break; case UNKNOWN: // FALL-THRU - Can not happen due to ensureChildren call above. case EMPTY: // FALL-THRU default: break; } if (getFirstLetterStyle() != null) { c.setFirstLettersTracker( c.getFirstLettersTracker().withOutLast()); } if (getFirstLineStyle() != null) { c.setFirstLinesTracker( c.getFirstLinesTracker().withOutLast()); } setState(Box.DONE); } protected void layoutInlineChildren( LayoutContext c, int contentStart, int breakAtLine, boolean tryAgain) { InlineBoxing.layoutContent(c, this, contentStart, breakAtLine); if (c.isPrint() && c.isPageBreaksAllowed() && getChildCount() > 1) { satisfyWidowsAndOrphans(c, contentStart, tryAgain); } if (tryAgain && (getStyle().isTextJustify())) { justifyText(c); } } private void justifyText(LayoutContext c) { for (Iterator i = getChildIterator(); i.hasNext(); ) { LineBox line = (LineBox)i.next(); line.justify(c); } } /** * TERMINOLOGY: * Orphans refers to the number of lines of content in this * box before the first page break. * Widows refers to the number of lines of content on the last page. *

* METHOD AIM: * This method aims (but can not guarantee) to satisfy the orphans and * widows CSS properties. Each of these provide a number * specifying a minimum number of content lines. *

* HOW: * By inserting page breaks, either before this box or between certain * lines in this box. *

* PREREQUISITES: * That the content of this box is CONTENT_INLINE and layout has * been done on this box. This means that the children of this box will consist * entirely of LineBox objects. */ private void satisfyWidowsAndOrphans(LayoutContext c, int contentStart, boolean tryAgain) { int orphans = (int) getStyle().asFloat(CSSName.ORPHANS); int widows = (int) getStyle().asFloat(CSSName.WIDOWS); if (orphans == 0 && widows == 0) { return; } LineBox firstLineBox = (LineBox)getChild(0); PageBox firstPage = c.getRootLayer().getFirstPage(c, firstLineBox); if (firstPage == null) { return; } int noContentLBs = 0; int i = 0; int cCount = getChildCount(); // First count the number of lines on the first page. while (i < cCount) { LineBox lB = (LineBox)getChild(i); if (lB.getAbsY() >= firstPage.getBottom(c)) { break; } if (! lB.isContainsContent()) { noContentLBs++; } i++; } // Check if all lines are on the one page. if (i != cCount) { if (i - noContentLBs < orphans) { // We don't have enough lines on first page. setNeedPageClear(true); } else { // We have to check the last page for widows. LineBox lastLineBox = (LineBox)getChild(cCount-1); PageBox lastPage = c.getRootLayer().getFirstPage(c, lastLineBox.getAbsY()); noContentLBs = 0; i = cCount - 1; int lastPageLineCount = 0; // Going backwards, count lines on the last page. while (i >= 0) { LineBox lB = (LineBox) getChild(i); if (lB.getAbsY() < lastPage.getTop()) { break; } if (! lB.isContainsContent()) { noContentLBs++; } i--; lastPageLineCount++; } lastPageLineCount -= noContentLBs; if (lastPageLineCount < widows) { // We don't have enough lines on last page. if (cCount - 1 - widows < orphans) { // If adding a page break to satisfy widows property would // break orphans constraint insert a page break at start. setNeedPageClear(true); } else if (tryAgain) { // Else, if we are allowed, lay out our line boxes with // a page break inserted after breakAtLine. int breakAtLine = cCount - 1 - widows; resetChildren(c); removeAllChildren(); layoutInlineChildren(c, contentStart, breakAtLine, false); } } } } } /** * See {@link ContentType} */ public ContentType getChildrenContentType() { return _childrenContentType; } /** * See {@link ContentType} */ public void setChildrenContentType(ContentType contentType) { _childrenContentType = contentType; } /** * See {@link #setInlineContent(List)} */ public List getInlineContent() { return _inlineContent; } /** * Inline content is created by the box builder. * It is important to note that the inline content here is stored in * the pre-layout state. Ie. It has not been flowed out into * {@link LineBox} and {@link InlineLayoutBox} objects but is stored * as {@link InlineBox} and block boxes that are laid out inline such * as inline-block and inline-table. *

* During layout inline-content is laid out into lines and so on but * the inline content is left untouched so as to be able to run layout * multiple times to satisfy constraints. *

* This method should be called with {@link #setChildrenContentType(ContentType)} set * to {@link ContentType#INLINE} as block boxes can not contain mixed content. */ public void setInlineContent(List inlineContent) { _inlineContent = inlineContent; if (inlineContent != null) { for (Styleable child : inlineContent) { if (child instanceof Box) { ((Box) child).setContainingBlock(this); } } } } protected boolean isSkipWhenCollapsingMargins() { return false; } protected boolean isMayCollapseMarginsWithChildren() { return (! isRoot()) && getStyle().isMayCollapseMarginsWithChildren(); } // This will require a rethink if we ever truly layout incrementally // Should only ever collapse top margin and pick up collapsable // bottom margins by looking back up the tree. protected void collapseMargins(LayoutContext c) { if (! isTopMarginCalculated() || ! isBottomMarginCalculated()) { recalcMargin(c); RectPropertySet margin = getMargin(c); if (! isTopMarginCalculated() && ! isBottomMarginCalculated() && isVerticalMarginsAdjoin(c)) { MarginCollapseResult collapsedMargin = _pendingCollapseCalculation != null ? _pendingCollapseCalculation : new MarginCollapseResult(); collapseEmptySubtreeMargins(c, collapsedMargin); setCollapsedBottomMargin(c, margin, collapsedMargin); } else { if (! isTopMarginCalculated()) { MarginCollapseResult collapsedMargin = _pendingCollapseCalculation != null ? _pendingCollapseCalculation : new MarginCollapseResult(); collapseTopMargin(c, true, collapsedMargin); if ((int) margin.top() != collapsedMargin.getMargin()) { setMarginTop(c, collapsedMargin.getMargin()); } } if (! isBottomMarginCalculated()) { MarginCollapseResult collapsedMargin = new MarginCollapseResult(); collapseBottomMargin(c, true, collapsedMargin); setCollapsedBottomMargin(c, margin, collapsedMargin); } } } } private void setCollapsedBottomMargin(LayoutContext c, RectPropertySet margin, MarginCollapseResult collapsedMargin) { BlockBox next = null; if (! isInline()) { next = getNextCollapsableSibling(collapsedMargin); } if (! (next == null || next instanceof AnonymousBlockBox) && collapsedMargin.hasMargin()) { next._pendingCollapseCalculation = collapsedMargin; setMarginBottom(c, 0); } else if ((int) margin.bottom() != collapsedMargin.getMargin()) { setMarginBottom(c, collapsedMargin.getMargin()); } } protected BlockBox getNextCollapsableSibling(MarginCollapseResult collapsedMargin) { BlockBox next = (BlockBox) getNextSibling(); while (next != null) { if (next instanceof AnonymousBlockBox) { ((AnonymousBlockBox) next).provideSiblingMarginToFloats( collapsedMargin.getMargin()); } if (! next.isSkipWhenCollapsingMargins()) { break; } else { next = (BlockBox) next.getNextSibling(); } } return next; } private void collapseTopMargin( LayoutContext c, boolean calculationRoot, MarginCollapseResult result) { if (! isTopMarginCalculated()) { if (! isSkipWhenCollapsingMargins()) { calcDimensions(c); if (c.isPrint() && getStyle().isDynamicAutoWidthApplicable()) { // Force recalculation once box is positioned setDimensionsCalculated(false); } RectPropertySet margin = getMargin(c); result.update((int) margin.top()); if (! calculationRoot && (int) margin.top() != 0) { setMarginTop(c, 0); } if (isMayCollapseMarginsWithChildren() && isNoTopPaddingOrBorder(c)) { ensureChildren(c); if (getChildrenContentType() == ContentType.BLOCK) { for (Iterator i = getChildIterator(); i.hasNext();) { BlockBox child = (BlockBox) i.next(); child.collapseTopMargin(c, false, result); if (child.isSkipWhenCollapsingMargins()) { continue; } break; } } } } setTopMarginCalculated(true); } } private void collapseBottomMargin( LayoutContext c, boolean calculationRoot, MarginCollapseResult result) { if (! isBottomMarginCalculated()) { if (! isSkipWhenCollapsingMargins()) { calcDimensions(c); if (c.isPrint() && getStyle().isDynamicAutoWidthApplicable()) { // Force recalculation once box is positioned setDimensionsCalculated(false); } RectPropertySet margin = getMargin(c); result.update((int) margin.bottom()); if (! calculationRoot && (int) margin.bottom() != 0) { setMarginBottom(c, 0); } if (isMayCollapseMarginsWithChildren() && ! getStyle().isTable() && isNoBottomPaddingOrBorder(c)) { ensureChildren(c); if (getChildrenContentType() == ContentType.BLOCK) { for (int i = getChildCount() - 1; i >= 0; i--) { BlockBox child = (BlockBox) getChild(i); if (child.isSkipWhenCollapsingMargins()) { continue; } child.collapseBottomMargin(c, false, result); break; } } } } setBottomMarginCalculated(true); } } private boolean isNoTopPaddingOrBorder(LayoutContext c) { RectPropertySet padding = getPadding(c); BorderPropertySet border = getBorder(c); return (int) padding.top() == 0 && (int) border.top() == 0; } private boolean isNoBottomPaddingOrBorder(LayoutContext c) { RectPropertySet padding = getPadding(c); BorderPropertySet border = getBorder(c); return (int) padding.bottom() == 0 && (int) border.bottom() == 0; } private void collapseEmptySubtreeMargins(LayoutContext c, MarginCollapseResult result) { RectPropertySet margin = getMargin(c); result.update((int) margin.top()); result.update((int) margin.bottom()); setMarginTop(c, 0); setTopMarginCalculated(true); setMarginBottom(c, 0); setBottomMarginCalculated(true); ensureChildren(c); if (getChildrenContentType() == ContentType.BLOCK) { for (Iterator i = getChildIterator(); i.hasNext();) { BlockBox child = (BlockBox) i.next(); child.collapseEmptySubtreeMargins(c, result); } } } private boolean isVerticalMarginsAdjoin(LayoutContext c) { CalculatedStyle style = getStyle(); BorderPropertySet borderWidth = style.getBorder(c); RectPropertySet padding = getPadding(c); boolean bordersOrPadding = (int) borderWidth.top() != 0 || (int) borderWidth.bottom() != 0 || (int) padding.top() != 0 || (int) padding.bottom() != 0; if (bordersOrPadding) { return false; } ensureChildren(c); if (getChildrenContentType() == ContentType.INLINE) { return false; } else if (getChildrenContentType() == ContentType.BLOCK) { for (Iterator i = getChildIterator(); i.hasNext();) { BlockBox child = (BlockBox) i.next(); if (child.isSkipWhenCollapsingMargins() || ! child.isVerticalMarginsAdjoin(c)) { return false; } } } return style.asFloat(CSSName.MIN_HEIGHT) == 0 && (isAutoHeight() || style.asFloat(CSSName.HEIGHT) == 0); } public boolean isTopMarginCalculated() { return _topMarginCalculated; } public void setTopMarginCalculated(boolean topMarginCalculated) { _topMarginCalculated = topMarginCalculated; } public boolean isBottomMarginCalculated() { return _bottomMarginCalculated; } public void setBottomMarginCalculated(boolean bottomMarginCalculated) { _bottomMarginCalculated = bottomMarginCalculated; } protected int getCSSWidth(CssContext c) { return getCSSWidth(c, false); } protected int getCSSWidth(CssContext c, boolean shrinkingToFit) { if (! isAnonymous()) { if (! getStyle().isAutoWidth()) { if (shrinkingToFit && ! getStyle().isAbsoluteWidth()) { return -1; } else { int result = (int) getStyle().getFloatPropertyProportionalWidth( CSSName.WIDTH, getContainingBlock().getContentWidth(), c); return result >= 0 ? result : -1; } } } return -1; } protected int getCSSFitToWidth(CssContext c) { if (! isAnonymous()) { if (! getStyle().isIdent(CSSName.FS_FIT_IMAGES_TO_WIDTH, IdentValue.AUTO)) { int result = (int) getStyle().getFloatPropertyProportionalWidth( CSSName.FS_FIT_IMAGES_TO_WIDTH, getContainingBlock().getContentWidth(), c); return result >= 0 ? result : -1; } } return -1; } protected int getCSSHeight(CssContext c) { if (! isAnonymous()) { if (! isAutoHeight()) { if (getStyle().hasAbsoluteUnit(CSSName.HEIGHT)) { return (int)getStyle().getFloatPropertyProportionalHeight(CSSName.HEIGHT, 0, c); } else { return (int)getStyle().getFloatPropertyProportionalHeight( CSSName.HEIGHT, ((BlockBox)getContainingBlock()).getCSSHeight(c), c); } } } return -1; } public boolean isAutoHeight() { if (getStyle().isAutoHeight()) { return true; } else if (getStyle().hasAbsoluteUnit(CSSName.HEIGHT)) { return false; } else { // We have a percentage height, defer to our block parent (if applicable) Box cb = getContainingBlock(); if (cb.isStyled() && (cb instanceof BlockBox)) { return ((BlockBox)cb).isAutoHeight(); } else return !(cb instanceof BlockBox) || !cb.isInitialContainingBlock(); } } private int getCSSMinWidth(CssContext c) { return getStyle().getMinWidth(c, getContainingBlockWidth()); } private int getCSSMaxWidth(CssContext c) { return getStyle().getMaxWidth(c, getContainingBlockWidth()); } private int getCSSMinHeight(CssContext c) { return getStyle().getMinHeight(c, getContainingBlockCSSHeight(c)); } private int getCSSMaxHeight(CssContext c) { return getStyle().getMaxHeight(c, getContainingBlockCSSHeight(c)); } // Use only when the height of the containing block is required for // resolving percentage values. Does not represent the actual (resolved) height // of the containing block. private int getContainingBlockCSSHeight(CssContext c) { if (! getContainingBlock().isStyled() || getContainingBlock().getStyle().isAutoHeight()) { return 0; } else { if (getContainingBlock().getStyle().hasAbsoluteUnit(CSSName.HEIGHT)) { return (int) getContainingBlock().getStyle().getFloatPropertyProportionalTo( CSSName.HEIGHT, 0, c); } else { return 0; } } } private int calcShrinkToFitWidth(LayoutContext c) { calcMinMaxWidth(c); return Math.min(Math.max(getMinWidth(), getAvailableWidth(c)), getMaxWidth()); } protected int getAvailableWidth(LayoutContext c) { if (! getStyle().isAbsolute()) { return getContainingBlockWidth(); } else { int left = 0; int right = 0; if (! getStyle().isIdent(CSSName.LEFT, IdentValue.AUTO)) { left = (int) getStyle().getFloatPropertyProportionalTo(CSSName.LEFT, getContainingBlock().getContentWidth(), c); } if (! getStyle().isIdent(CSSName.RIGHT, IdentValue.AUTO)) { right = (int) getStyle().getFloatPropertyProportionalTo(CSSName.RIGHT, getContainingBlock().getContentWidth(), c); } return getContainingBlock().getPaddingWidth(c) - left - right; } } protected boolean isFixedWidthAdvisoryOnly() { return false; } private void recalcMargin(LayoutContext c) { if (isTopMarginCalculated() && isBottomMarginCalculated()) { return; } // Check if we're a potential candidate upfront to avoid expensive // getStyleMargin(c, false) call FSDerivedValue topMargin = getStyle().valueByName(CSSName.MARGIN_TOP); boolean resetTop = topMargin instanceof LengthValue && ! topMargin.hasAbsoluteUnit(); FSDerivedValue bottomMargin = getStyle().valueByName(CSSName.MARGIN_BOTTOM); boolean resetBottom = bottomMargin instanceof LengthValue && ! bottomMargin.hasAbsoluteUnit(); if (! resetTop && ! resetBottom) { return; } RectPropertySet styleMargin = getStyleMargin(c, false); RectPropertySet workingMargin = getMargin(c); // A shrink-to-fit calculation may have set incorrect values for // percentage margins (as the containing block width // hasn't been calculated yet). Reset top and bottom margins // in this case. if (! isTopMarginCalculated() && styleMargin.top() != workingMargin.top()) { setMarginTop(c, (int) styleMargin.top()); } if (! isBottomMarginCalculated() && styleMargin.bottom() != workingMargin.bottom()) { setMarginBottom(c, (int) styleMargin.bottom()); } } public void calcMinMaxWidth(LayoutContext c) { if (! isMinMaxCalculated()) { RectPropertySet margin = getMargin(c); BorderPropertySet border = getBorder(c); RectPropertySet padding = getPadding(c); int width = getCSSWidth(c, true); createReplaced(c); if (isReplaced() && width == -1) { // FIXME: We need to special case this for issue 313. width = getContentWidth(); } if (width != -1 && !isFixedWidthAdvisoryOnly()) { _minWidth = _maxWidth = (int) margin.left() + (int) border.left() + (int) padding.left() + width + (int) margin.right() + (int) border.right() + (int) padding.right(); } else { int cw = -1; if (width != -1) { // Set a provisional content width on table cells so // percentage values resolve correctly (but save and reset // the existing value) cw = getContentWidth(); setContentWidth(width); } _minWidth = _maxWidth = (int) margin.left() + (int) border.left() + (int) padding.left() + (int) margin.right() + (int) border.right() + (int) padding.right(); int minimumMaxWidth = _maxWidth; if (width != -1) { minimumMaxWidth += width; } ensureChildren(c); if (getChildrenContentType() == ContentType.BLOCK) { calcMinMaxWidthBlockChildren(c); } else if (getChildrenContentType() == ContentType.INLINE) { calcMinMaxWidthInlineChildren(c); } if (minimumMaxWidth > _maxWidth) { _maxWidth = minimumMaxWidth; } if (cw != -1) { setContentWidth(cw); } } if (! isReplaced()) { calcMinMaxCSSMinMaxWidth(c, margin, border, padding); } setMinMaxCalculated(true); } } private void calcMinMaxCSSMinMaxWidth( LayoutContext c, RectPropertySet margin, BorderPropertySet border, RectPropertySet padding) { int cssMinWidth = getCSSMinWidth(c); if (cssMinWidth > 0) { cssMinWidth += (int) margin.left() + (int) border.left() + (int) padding.left() + (int) margin.right() + (int) border.right() + (int) padding.right(); if (_minWidth < cssMinWidth) { _minWidth = cssMinWidth; } } if (! getStyle().isMaxWidthNone()) { int cssMaxWidth = getCSSMaxWidth(c); cssMaxWidth += (int) margin.left() + (int) border.left() + (int) padding.left() + (int) margin.right() + (int) border.right() + (int) padding.right(); if (_maxWidth > cssMaxWidth) { if (cssMaxWidth > _minWidth) { _maxWidth = cssMaxWidth; } else { _maxWidth = _minWidth; } } } } private void calcMinMaxWidthBlockChildren(LayoutContext c) { int childMinWidth = 0; int childMaxWidth = 0; for (Iterator i = getChildIterator(); i.hasNext();) { BlockBox child = (BlockBox) i.next(); child.calcMinMaxWidth(c); if (child.getMinWidth() > childMinWidth) { childMinWidth = child.getMinWidth(); } if (child.getMaxWidth() > childMaxWidth) { childMaxWidth = child.getMaxWidth(); } } _minWidth += childMinWidth; _maxWidth += childMaxWidth; } private void calcMinMaxWidthInlineChildren(LayoutContext c) { int textIndent = (int) getStyle().getFloatPropertyProportionalWidth( CSSName.TEXT_INDENT, getContentWidth(), c); if (getStyle().isListItem() && getStyle().isListMarkerInside()) { createMarkerData(c); textIndent += getMarkerData().getLayoutWidth(); } int childMinWidth = 0; int childMaxWidth = 0; int lineWidth = 0; InlineBox trimmableIB = null; for (Styleable child : _inlineContent) { if (child.getStyle().isAbsolute() || child.getStyle().isFixed() || child.getStyle().isRunning()) { continue; } if (child.getStyle().isFloated() || child.getStyle().isInlineBlock() || child.getStyle().isInlineTable()) { if (child.getStyle().isFloated() && child.getStyle().isCleared()) { if (trimmableIB != null) { lineWidth -= trimmableIB.getTrailingSpaceWidth(c); } if (lineWidth > childMaxWidth) { childMaxWidth = lineWidth; } lineWidth = 0; } trimmableIB = null; BlockBox block = (BlockBox) child; block.calcMinMaxWidth(c); lineWidth += block.getMaxWidth(); if (block.getMinWidth() > childMinWidth) { childMinWidth = block.getMinWidth(); } } else { /* child.getStyle().isInline() */ InlineBox iB = (InlineBox) child; IdentValue whitespace = iB.getStyle().getWhitespace(); iB.calcMinMaxWidth(c, getContentWidth(), lineWidth == 0); if (whitespace == IdentValue.NOWRAP) { lineWidth += textIndent + iB.getMaxWidth(); if (iB.getMinWidth() > childMinWidth) { childMinWidth = iB.getMinWidth(); } trimmableIB = iB; } else if (whitespace == IdentValue.PRE) { if (trimmableIB != null) { lineWidth -= trimmableIB.getTrailingSpaceWidth(c); } trimmableIB = null; if (lineWidth > childMaxWidth) { childMaxWidth = lineWidth; } lineWidth = textIndent + iB.getFirstLineWidth(); if (lineWidth > childMinWidth) { childMinWidth = lineWidth; } lineWidth = iB.getMaxWidth(); if (lineWidth > childMinWidth) { childMinWidth = lineWidth; } if (childMinWidth > childMaxWidth) { childMaxWidth = childMinWidth; } lineWidth = 0; } else if (whitespace == IdentValue.PRE_WRAP || whitespace == IdentValue.PRE_LINE) { lineWidth += textIndent + iB.getFirstLineWidth(); if (trimmableIB != null) { lineWidth -= trimmableIB.getTrailingSpaceWidth(c); } if (lineWidth > childMaxWidth) { childMaxWidth = lineWidth; } if (iB.getMaxWidth() > childMaxWidth) { childMaxWidth = iB.getMaxWidth(); } if (iB.getMinWidth() > childMinWidth) { childMinWidth = iB.getMinWidth(); } if (whitespace == IdentValue.PRE_LINE) { trimmableIB = iB; } else { trimmableIB = null; } lineWidth = 0; } else /* if (whitespace == IdentValue.NORMAL) */ { lineWidth += textIndent + iB.getMaxWidth(); if (iB.getMinWidth() > childMinWidth) { childMinWidth = textIndent + iB.getMinWidth(); } trimmableIB = iB; } if (textIndent > 0) { textIndent = 0; } } } if (trimmableIB != null) { lineWidth -= trimmableIB.getTrailingSpaceWidth(c); } if (lineWidth > childMaxWidth) { childMaxWidth = lineWidth; } _minWidth += childMinWidth; _maxWidth += childMaxWidth; } public int getMaxWidth() { return _maxWidth; } protected void setMaxWidth(int maxWidth) { _maxWidth = maxWidth; } public int getMinWidth() { return _minWidth; } protected void setMinWidth(int minWidth) { _minWidth = minWidth; } public void styleText(LayoutContext c) { styleText(c, getStyle()); } // FIXME Should be expanded into generic restyle facility public void styleText(LayoutContext c, CalculatedStyle style) { if (getChildrenContentType() == ContentType.INLINE) { LinkedList styles = new LinkedList<>(); styles.add(style); for (Object a_inlineContent : _inlineContent) { Styleable child = (Styleable) a_inlineContent; if (child instanceof InlineBox) { InlineBox iB = (InlineBox) child; if (iB.isStartsHere()) { CascadedStyle cs = null; if (iB.getElement() != null) { if (iB.getPseudoElementOrClass() == null) { cs = c.getCss().getCascadedStyle(iB.getElement(), false); } else { cs = c.getCss().getPseudoElementStyle( iB.getElement(), iB.getPseudoElementOrClass()); } styles.add(styles.getLast().deriveStyle(cs)); } else { styles.add(style.createAnonymousStyle(IdentValue.INLINE)); } } iB.setStyle(styles.getLast()); iB.applyTextTransform(); if (iB.isEndsHere()) { styles.removeLast(); } } } } } @Override protected void calcChildPaintingInfo( final CssContext c, final PaintingInfo result, final boolean useCache) { if (getPersistentBFC() != null) { (this).getPersistentBFC().getFloatManager().performFloatOperation( new FloatManager.FloatOperation() { @Override public void operate(Box floater) { PaintingInfo info = floater.calcPaintingInfo(c, useCache); moveIfGreater( result.getOuterMarginCorner(), info.getOuterMarginCorner()); } }); } super.calcChildPaintingInfo(c, result, useCache); } public CascadedStyle getFirstLetterStyle() { return _firstLetterStyle; } public void setFirstLetterStyle(CascadedStyle firstLetterStyle) { _firstLetterStyle = firstLetterStyle; } public CascadedStyle getFirstLineStyle() { return _firstLineStyle; } public void setFirstLineStyle(CascadedStyle firstLineStyle) { _firstLineStyle = firstLineStyle; } protected boolean isMinMaxCalculated() { return _minMaxCalculated; } protected void setMinMaxCalculated(boolean minMaxCalculated) { _minMaxCalculated = minMaxCalculated; } protected void setDimensionsCalculated(boolean dimensionsCalculated) { _dimensionsCalculated = dimensionsCalculated; } private boolean isDimensionsCalculated() { return _dimensionsCalculated; } protected void setNeedShrinkToFitCalculatation(boolean needShrinkToFitCalculatation) { _needShrinkToFitCalculatation = needShrinkToFitCalculatation; } private boolean isNeedShrinkToFitCalculatation() { return _needShrinkToFitCalculatation; } public void initStaticPos(LayoutContext c, BlockBox parent, int childOffset) { setX(0); setY(childOffset); } public int calcBaseline(LayoutContext c) { for (int i = 0; i < getChildCount(); i++) { Box b = getChild(i); if (b instanceof LineBox) { return b.getAbsY() + ((LineBox) b).getBaseline(); } else { if (b instanceof TableRowBox) { return b.getAbsY() + ((TableRowBox) b).getBaseline(); } else { int result = ((BlockBox) b).calcBaseline(c); if (result != NO_BASELINE) { return result; } } } } return NO_BASELINE; } protected int calcInitialBreakAtLine(LayoutContext c) { BreakAtLineContext bContext = c.getBreakAtLineContext(); if (bContext != null && bContext.getBlock() == this) { return bContext.getLine(); } return 0; } public boolean isCurrentBreakAtLineContext(LayoutContext c) { BreakAtLineContext bContext = c.getBreakAtLineContext(); return bContext != null && bContext.getBlock() == this; } public BreakAtLineContext calcBreakAtLineContext(LayoutContext c) { if (! c.isPrint() || ! getStyle().isKeepWithInline()) { return null; } LineBox breakLine = findLastNthLineBox((int)getStyle().asFloat(CSSName.WIDOWS)); if (breakLine != null) { PageBox linePage = c.getRootLayer().getLastPage(c, breakLine); PageBox ourPage = c.getRootLayer().getLastPage(c, this); if (linePage != null && ourPage != null && linePage.getPageNo() + 1 == ourPage.getPageNo()) { BlockBox breakBox = breakLine.getParent(); return new BreakAtLineContext(breakBox, breakBox.findOffset(breakLine)); } } return null; } public int calcInlineBaseline(CssContext c) { if (isReplaced() && getReplacedElement().hasBaseline()) { Rectangle bounds = getContentAreaEdge(getAbsX(), getAbsY(), c); return bounds.y + getReplacedElement().getBaseline() - getAbsY(); } else { LineBox lastLine = findLastLineBox(); if (lastLine == null) { return getHeight(); } else { return lastLine.getAbsY() + lastLine.getBaseline() - getAbsY(); } } } public int findOffset(Box box) { int ccount = getChildCount(); for (int i = 0; i < ccount; i++) { if (getChild(i) == box) { return i; } } return -1; } public LineBox findLastNthLineBox(int count) { LastLineBoxContext context = new LastLineBoxContext(count); findLastLineBox(context); return context.line; } private static class LastLineBoxContext { public int current; public LineBox line; public LastLineBoxContext(int i) { this.current = i; } } private void findLastLineBox(LastLineBoxContext context) { ContentType type = getChildrenContentType(); int ccount = getChildCount(); if (ccount > 0) { if (type == ContentType.INLINE) { for (int i = ccount - 1; i >= 0; i--) { LineBox child = (LineBox) getChild(i); if (child.getHeight() > 0) { context.line = child; if (--context.current == 0) { return; } } } } else if (type == ContentType.BLOCK) { for (int i = ccount - 1; i >= 0; i--) { ((BlockBox) getChild(i)).findLastLineBox(context); if (context.current == 0) { break; } } } } } private LineBox findLastLineBox() { ContentType type = getChildrenContentType(); int ccount = getChildCount(); if (ccount > 0) { if (type == ContentType.INLINE) { for (int i = ccount - 1; i >= 0; i--) { LineBox result = (LineBox) getChild(i); if (result.getHeight() > 0) { return result; } } } else if (type == ContentType.BLOCK) { for (int i = ccount - 1; i >= 0; i--) { LineBox result = ((BlockBox) getChild(i)).findLastLineBox(); if (result != null) { return result; } } } } return null; } private LineBox findFirstLineBox() { ContentType type = getChildrenContentType(); int ccount = getChildCount(); if (ccount > 0) { if (type == ContentType.INLINE) { for (int i = 0; i < ccount; i++) { LineBox result = (LineBox) getChild(i); if (result.getHeight() > 0) { return result; } } } else if (type == ContentType.BLOCK) { for (int i = 0; i < ccount; i++) { LineBox result = ((BlockBox) getChild(i)).findFirstLineBox(); if (result != null) { return result; } } } } return null; } public boolean isNeedsKeepWithInline(LayoutContext c) { if (c.isPrint() && getStyle().isKeepWithInline()) { LineBox line = findFirstLineBox(); if (line != null) { PageBox linePage = c.getRootLayer().getFirstPage(c, line); PageBox ourPage = c.getRootLayer().getFirstPage(c, this); return linePage != null && ourPage != null && linePage.getPageNo() == ourPage.getPageNo()+1; } } return false; } public boolean isFloated() { return _floatedBoxData != null; } public FloatedBoxData getFloatedBoxData() { return _floatedBoxData; } public void setFloatedBoxData(FloatedBoxData floatedBoxData) { _floatedBoxData = floatedBoxData; } public int getChildrenHeight() { return _childrenHeight; } protected void setChildrenHeight(int childrenHeight) { _childrenHeight = childrenHeight; } public boolean isFromCaptionedTable() { return _fromCaptionedTable; } public void setFromCaptionedTable(boolean fromTable) { _fromCaptionedTable = fromTable; } @Override protected boolean isInlineBlock() { return isInline(); } public boolean isInMainFlow() { Box flowRoot = rootBox(); return flowRoot.isRoot(); } @Override public Box getDocumentParent() { Box staticEquivalent = getStaticEquivalent(); if (staticEquivalent != null) { return staticEquivalent; } else { return getParent(); } } public boolean isContainsInlineContent(LayoutContext c) { ensureChildren(c); switch (getChildrenContentType()) { case INLINE: return true; case EMPTY: return false; case BLOCK: return getChildren().stream().anyMatch(box -> ((BlockBox) box).isContainsInlineContent(c)); case UNKNOWN: // FALL-THRU - Can not happen due to ensureChildren call above. default: throw new RuntimeException("internal error: no children"); } } public boolean checkPageContext(LayoutContext c) { if (! getStyle().isIdent(CSSName.PAGE, IdentValue.AUTO)) { String pageName = getStyle().getStringProperty(CSSName.PAGE); if (!pageName.equals(c.getPageName()) && isInDocumentFlow() && (shouldBeReplaced() || isContainsInlineContent(c))) { c.setPendingPageName(pageName); return true; } } else if (c.getPageName() != null && isInDocumentFlow()) { c.setPendingPageName(null); return true; } return false; } public boolean isNeedsClipOnPaint(CssContext c) { return ! isReplaced() && getStyle().isIdent(CSSName.OVERFLOW, IdentValue.HIDDEN) && getStyle().isOverflowApplies(); } protected void propagateExtraSpace( LayoutContext c, ContentLimitContainer parentContainer, ContentLimitContainer currentContainer, int extraTop, int extraBottom) { int start = currentContainer.getInitialPageNo(); int end = currentContainer.getLastPageNo(); int current = start; while (current <= end) { ContentLimit contentLimit = currentContainer.getContentLimit(current); if (current != start) { int top = contentLimit.getTop(); if (top != ContentLimit.UNDEFINED) { parentContainer.updateTop(c, top - extraTop); } } if (current != end) { int bottom = contentLimit.getBottom(); if (bottom != ContentLimit.UNDEFINED) { parentContainer.updateBottom(c, bottom + extraBottom); } } current++; } } @Override public void collectLayoutText(LayoutContext c, StringBuilder builder) { if (_childrenContentType == BlockBox.ContentType.INLINE) { for (Styleable s : getInlineContent()) { if (s instanceof InlineBox) { builder.append(((InlineBox) s).getText()); } else if (s instanceof BlockBox) { ((BlockBox) s).collectLayoutText(c, builder); } } } else if (_childrenContentType == BlockBox.ContentType.BLOCK) { for (Box box : getChildren()) { if (box instanceof BlockBox) { ((BlockBox) box).collectLayoutText(c, builder); } } } } public static class MarginCollapseResult { private int maxPositive; private int maxNegative; public void update(int value) { if (value < 0 && value < maxNegative) { maxNegative = value; } if (value > 0 && value > maxPositive) { maxPositive = value; } } public int getMargin() { return maxPositive + maxNegative; } public boolean hasMargin() { return maxPositive != 0 || maxNegative != 0; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy