Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.itextpdf.layout.renderer.BlockRenderer Maven / Gradle / Ivy
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2023 Apryse Group NV
Authors: Apryse Software.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
package com.itextpdf.layout.renderer;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.geom.AffineTransform;
import com.itextpdf.kernel.geom.Point;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.layout.MinMaxWidthLayoutResult;
import com.itextpdf.layout.layout.PositionedLayoutContext;
import com.itextpdf.layout.logs.LayoutLogMessageConstant;
import com.itextpdf.layout.margincollapse.MarginsCollapseHandler;
import com.itextpdf.layout.margincollapse.MarginsCollapseInfo;
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
import com.itextpdf.layout.minmaxwidth.MinMaxWidthUtils;
import com.itextpdf.layout.properties.AreaBreakType;
import com.itextpdf.layout.properties.FloatPropertyValue;
import com.itextpdf.layout.properties.OverflowPropertyValue;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
import com.itextpdf.layout.properties.VerticalAlignment;
import com.itextpdf.layout.properties.ClearPropertyValue;
import com.itextpdf.layout.tagging.LayoutTaggingHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a renderer for block elements.
*/
public abstract class BlockRenderer extends AbstractRenderer {
/**
* Creates a BlockRenderer from its corresponding layout object.
*
* @param modelElement the {@link IElement} which this object should manage
*/
protected BlockRenderer(IElement modelElement) {
super(modelElement);
}
/**
* {@inheritDoc}
*/
@Override
public LayoutResult layout(LayoutContext layoutContext) {
this.isLastRendererForModelElement = true;
Map waitingFloatsSplitRenderers = new LinkedHashMap<>();
List waitingOverflowFloatRenderers = new ArrayList<>();
boolean floatOverflowedCompletely = false;
boolean wasHeightClipped = false;
boolean wasParentsHeightClipped = layoutContext.isClippedHeight();
int pageNumber = layoutContext.getArea().getPageNumber();
boolean isPositioned = isPositioned();
Rectangle parentBBox = layoutContext.getArea().getBBox().clone();
List floatRendererAreas = layoutContext.getFloatRendererAreas();
FloatPropertyValue floatPropertyValue = this.getProperty(Property.FLOAT);
Float rotation = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
OverflowPropertyValue overflowX = this.getProperty(Property.OVERFLOW_X);
MarginsCollapseHandler marginsCollapseHandler = null;
boolean marginsCollapsingEnabled = Boolean.TRUE.equals(getPropertyAsBoolean(Property.COLLAPSING_MARGINS));
if (marginsCollapsingEnabled) {
marginsCollapseHandler = new MarginsCollapseHandler(this, layoutContext.getMarginsCollapseInfo());
}
Float blockWidth = retrieveWidth(parentBBox.getWidth());
if (rotation != null || isFixedLayout()) {
parentBBox.moveDown(AbstractRenderer.INF - parentBBox.getHeight()).setHeight(AbstractRenderer.INF);
}
if (rotation != null && !FloatingHelper.isRendererFloating(this, floatPropertyValue)) {
blockWidth = RotationUtils.retrieveRotatedLayoutWidth(parentBBox.getWidth(), this);
}
boolean includeFloatsInOccupiedArea = BlockFormattingContextUtil.isRendererCreateBfc(this);
float clearHeightCorrection = FloatingHelper.calculateClearHeightCorrection(this, floatRendererAreas, parentBBox);
FloatingHelper.applyClearance(parentBBox, marginsCollapseHandler, clearHeightCorrection, FloatingHelper.isRendererFloating(this));
if (FloatingHelper.isRendererFloating(this, floatPropertyValue)) {
blockWidth = FloatingHelper.adjustFloatedBlockLayoutBox(this, parentBBox, blockWidth, floatRendererAreas, floatPropertyValue, overflowX);
floatRendererAreas = new ArrayList<>();
}
boolean isCellRenderer = this instanceof CellRenderer;
if (marginsCollapsingEnabled) {
marginsCollapseHandler.startMarginsCollapse(parentBBox);
}
Border[] borders = getBorders();
UnitValue[] paddings = getPaddings();
applyMargins(parentBBox, false);
applyBorderBox(parentBBox, borders, false);
if (isFixedLayout()) {
parentBBox.setX((float) this.getPropertyAsFloat(Property.LEFT));
}
applyPaddings(parentBBox, paddings, false);
Float blockMaxHeight = retrieveMaxHeight();
OverflowPropertyValue overflowY = (null == blockMaxHeight || blockMaxHeight > parentBBox.getHeight())
&& !wasParentsHeightClipped
? OverflowPropertyValue.FIT
: this.getProperty(Property.OVERFLOW_Y);
applyWidth(parentBBox, blockWidth, overflowX);
wasHeightClipped = applyMaxHeight(parentBBox, blockMaxHeight, marginsCollapseHandler, isCellRenderer, wasParentsHeightClipped, overflowY);
List areas;
if (isPositioned) {
areas = Collections.singletonList(parentBBox);
} else {
areas = initElementAreas(new LayoutArea(pageNumber, parentBBox));
}
occupiedArea = new LayoutArea(pageNumber, new Rectangle(parentBBox.getX(), parentBBox.getY() + parentBBox.getHeight(), parentBBox.getWidth(), 0));
shrinkOccupiedAreaForAbsolutePosition();
TargetCounterHandler.addPageByID(this);
int currentAreaPos = 0;
Rectangle layoutBox = areas.get(0).clone();
// rectangles are compared by instances
Set nonChildFloatingRendererAreas = new HashSet<>(floatRendererAreas);
// the first renderer (one of childRenderers or their children) to produce LayoutResult.NOTHING
IRenderer causeOfNothing = null;
boolean anythingPlaced = false;
// We have to remember initial FORCED_PLACEMENT property of this renderer to use it later
// to define if rotated content should be placed or not
final boolean initialForcePlacementForRotationAdjustments =
Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT));
for (int childPos = 0; childPos < childRenderers.size(); childPos++) {
IRenderer childRenderer = childRenderers.get(childPos);
LayoutResult result;
childRenderer.setParent(this);
MarginsCollapseInfo childMarginsInfo = null;
if (floatOverflowedCompletely && FloatingHelper.isRendererFloating(childRenderer)) {
waitingFloatsSplitRenderers.put(childPos, null);
waitingOverflowFloatRenderers.add(childRenderer);
continue;
}
if (!waitingOverflowFloatRenderers.isEmpty() && FloatingHelper.isClearanceApplied(waitingOverflowFloatRenderers, childRenderer.getProperty(Property.CLEAR))) {
if (FloatingHelper.isRendererFloating(childRenderer)) {
waitingFloatsSplitRenderers.put(childPos, null);
waitingOverflowFloatRenderers.add(childRenderer);
floatOverflowedCompletely = true;
continue;
}
if (marginsCollapsingEnabled && !isCellRenderer) {
marginsCollapseHandler.endMarginsCollapse(layoutBox);
}
FloatingHelper.includeChildFloatsInOccupiedArea(floatRendererAreas, this, nonChildFloatingRendererAreas);
fixOccupiedAreaIfOverflowedX(overflowX, layoutBox);
result = new LayoutResult(LayoutResult.NOTHING, null, null, childRenderer);
boolean isKeepTogether = isKeepTogether(childRenderer);
int layoutResult = anythingPlaced && !isKeepTogether ? LayoutResult.PARTIAL : LayoutResult.NOTHING;
AbstractRenderer[] splitAndOverflowRenderers = createSplitAndOverflowRenderers(childPos, layoutResult, result, waitingFloatsSplitRenderers, waitingOverflowFloatRenderers);
AbstractRenderer splitRenderer = splitAndOverflowRenderers[0];
AbstractRenderer overflowRenderer = splitAndOverflowRenderers[1];
if (isKeepTogether) {
splitRenderer = null;
overflowRenderer.childRenderers.clear();
overflowRenderer.childRenderers = new ArrayList<>(childRenderers);
}
updateHeightsOnSplit(wasHeightClipped, splitRenderer, overflowRenderer);
applyPaddings(occupiedArea.getBBox(), paddings, true);
applyBorderBox(occupiedArea.getBBox(), borders, true);
applyMargins(occupiedArea.getBBox(), true);
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)) || wasHeightClipped) {
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
return new LayoutResult(LayoutResult.FULL, editedArea, splitRenderer, null, null);
} else {
if (layoutResult != LayoutResult.NOTHING) {
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
return new LayoutResult(layoutResult, editedArea, splitRenderer, overflowRenderer, null).setAreaBreak(result.getAreaBreak());
} else {
floatRendererAreas.retainAll(nonChildFloatingRendererAreas);
return new LayoutResult(layoutResult, null, null, overflowRenderer, result.getCauseOfNothing()).setAreaBreak(result.getAreaBreak());
}
}
}
if (marginsCollapsingEnabled) {
childMarginsInfo = startChildMarginsHandling(childRenderer, layoutBox, marginsCollapseHandler);
}
Rectangle changedLayoutBox =
recalculateLayoutBoxBeforeChildLayout(layoutBox, childRenderer, areas.get(0).clone());
while ((result = childRenderer.setParent(this).layout(new LayoutContext(
new LayoutArea(pageNumber, changedLayoutBox),
childMarginsInfo,
floatRendererAreas,
wasHeightClipped || wasParentsHeightClipped)))
.getStatus() != LayoutResult.FULL) {
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FILL_AVAILABLE_AREA_ON_SPLIT))
|| Boolean.TRUE.equals(getPropertyAsBoolean(Property.FILL_AVAILABLE_AREA))) {
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), layoutBox));
} else if (result.getOccupiedArea() != null && result.getStatus() != LayoutResult.NOTHING) {
recalculateOccupiedAreaAfterChildLayout(result.getOccupiedArea().getBBox(), blockMaxHeight);
fixOccupiedAreaIfOverflowedX(overflowX, layoutBox);
}
if (marginsCollapsingEnabled && result.getStatus() != LayoutResult.NOTHING) {
marginsCollapseHandler.endChildMarginsHandling(layoutBox);
}
if (FloatingHelper.isRendererFloating(childRenderer)) {
// Check if current block is empty, kid returns nothing and neither floats nor content
// were met on root area (e.g. page area) - return NOTHING, don't layout other kids,
// expect FORCED_PLACEMENT to be set.
boolean immediatelyReturnNothing = result.getStatus() == LayoutResult.NOTHING
&& !anythingPlaced
&& floatRendererAreas.isEmpty()
&& isFirstOnRootArea();
if (!immediatelyReturnNothing) {
waitingFloatsSplitRenderers.put(childPos, result.getStatus() == LayoutResult.PARTIAL ? result.getSplitRenderer() : null);
waitingOverflowFloatRenderers.add(result.getOverflowRenderer());
floatOverflowedCompletely = result.getStatus() == LayoutResult.NOTHING;
break;
}
}
if (marginsCollapsingEnabled) {
marginsCollapseHandler.endMarginsCollapse(layoutBox);
}
// On page split, content will be drawn on next page, i.e. under all floats on this page
FloatingHelper.includeChildFloatsInOccupiedArea(floatRendererAreas, this, nonChildFloatingRendererAreas);
fixOccupiedAreaIfOverflowedX(overflowX, layoutBox);
if (result.getSplitRenderer() != null) {
// TODO DEVSIX-6488 all elements should be layouted first in case when parent box should wrap around child boxes
alignChildHorizontally(result.getSplitRenderer(), occupiedArea.getBBox());
}
// Save the first renderer to produce LayoutResult.NOTHING
if (null == causeOfNothing && null != result.getCauseOfNothing()) {
causeOfNothing = result.getCauseOfNothing();
}
// have more areas
if (currentAreaPos + 1 < areas.size() && !(result.getAreaBreak() != null && result.getAreaBreak().getType() == AreaBreakType.NEXT_PAGE)) {
if (result.getStatus() == LayoutResult.PARTIAL) {
childRenderers.set(childPos, result.getSplitRenderer());
childRenderers.add(childPos + 1, result.getOverflowRenderer());
} else {
if (result.getOverflowRenderer() != null) {
childRenderers.set(childPos, result.getOverflowRenderer());
} else {
childRenderers.remove(childPos);
}
childPos--;
}
layoutBox = areas.get(++currentAreaPos).clone();
break;
} else {
final LayoutResult layoutResult = processNotFullChildResult(
layoutContext, waitingFloatsSplitRenderers, waitingOverflowFloatRenderers, wasHeightClipped,
floatRendererAreas, marginsCollapsingEnabled, clearHeightCorrection, borders, paddings,
areas, currentAreaPos, layoutBox, nonChildFloatingRendererAreas, causeOfNothing,
anythingPlaced, childPos, result);
if (layoutResult == null) {
layoutBox = areas.get(++currentAreaPos).clone();
break;
}
if (stopLayoutingChildrenIfChildResultNotFull(layoutResult)) {
return layoutResult;
}
result = layoutResult;
break;
}
}
anythingPlaced = anythingPlaced || result.getStatus() != LayoutResult.NOTHING;
handleForcedPlacement(anythingPlaced);
// The second condition check (after &&) is needed only if margins collapsing is enabled
if (result.getOccupiedArea() != null && (!FloatingHelper.isRendererFloating(childRenderer) || includeFloatsInOccupiedArea)) {
recalculateOccupiedAreaAfterChildLayout(result.getOccupiedArea().getBBox(), blockMaxHeight);
fixOccupiedAreaIfOverflowedX(overflowX, layoutBox);
}
if (marginsCollapsingEnabled) {
marginsCollapseHandler.endChildMarginsHandling(layoutBox);
}
if (result.getStatus() == LayoutResult.FULL) {
decreaseLayoutBoxAfterChildPlacement(layoutBox, result, childRenderer);
if (childRenderer.getOccupiedArea() != null) {
// TODO DEVSIX-6488 all elements should be layouted first in case when parent box should wrap around child boxes
alignChildHorizontally(childRenderer, occupiedArea.getBBox());
}
}
// Save the first renderer to produce LayoutResult.NOTHING
if (null == causeOfNothing && null != result.getCauseOfNothing()) {
causeOfNothing = result.getCauseOfNothing();
}
}
if (includeFloatsInOccupiedArea) {
FloatingHelper.includeChildFloatsInOccupiedArea(floatRendererAreas, this, nonChildFloatingRendererAreas);
fixOccupiedAreaIfOverflowedX(overflowX, layoutBox);
}
if (wasHeightClipped) {
fixOccupiedAreaIfOverflowedY(overflowY, layoutBox);
}
if (marginsCollapsingEnabled) {
marginsCollapseHandler.endMarginsCollapse(layoutBox);
}
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FILL_AVAILABLE_AREA))) {
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), layoutBox));
}
int layoutResult = LayoutResult.FULL;
boolean processOverflowedFloats = !waitingOverflowFloatRenderers.isEmpty() && !wasHeightClipped &&
!Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT));
AbstractRenderer overflowRenderer = null;
if (!includeFloatsInOccupiedArea || !processOverflowedFloats) {
overflowRenderer = applyMinHeight(overflowY, layoutBox);
}
boolean minHeightOverflow = overflowRenderer != null;
if (minHeightOverflow && isKeepTogether()) {
floatRendererAreas.retainAll(nonChildFloatingRendererAreas);
return new LayoutResult(LayoutResult.NOTHING, null, null, this, this);
}
// in this case layout result need to be changed
if (overflowRenderer != null || processOverflowedFloats) {
layoutResult = !anythingPlaced && !waitingOverflowFloatRenderers.isEmpty()
// nothing was placed and there are some overflowed floats
? LayoutResult.NOTHING
// either something was placed or (since there are no overflowed floats) there is overflow renderer
// that indicates overflowed min_height
: LayoutResult.PARTIAL;
}
if (processOverflowedFloats) {
if (overflowRenderer == null || layoutResult == LayoutResult.NOTHING) {
// if layout result is NOTHING - avoid possible usage of the overflowRenderer created
// for overflow of min_height with adjusted height properties
overflowRenderer = createOverflowRenderer(layoutResult);
}
overflowRenderer.getChildRenderers().addAll(waitingOverflowFloatRenderers);
if (layoutResult == LayoutResult.PARTIAL && !minHeightOverflow && !includeFloatsInOccupiedArea) {
FloatingHelper.removeParentArtifactsOnPageSplitIfOnlyFloatsOverflow(overflowRenderer);
}
}
AbstractRenderer splitRenderer = this;
if (waitingFloatsSplitRenderers.size() > 0 && layoutResult != LayoutResult.NOTHING) {
splitRenderer = createSplitRenderer(layoutResult);
splitRenderer.childRenderers = new ArrayList<>(childRenderers);
replaceSplitRendererKidFloats(waitingFloatsSplitRenderers, splitRenderer);
float usedHeight = occupiedArea.getBBox().getHeight();
if (!includeFloatsInOccupiedArea) {
Rectangle commonRectangle = Rectangle.getCommonRectangle(layoutBox, occupiedArea.getBBox());
usedHeight = commonRectangle.getHeight();
}
// this must be processed before margin/border/padding
updateHeightsOnSplit(usedHeight, wasHeightClipped, splitRenderer, overflowRenderer, includeFloatsInOccupiedArea);
}
if (positionedRenderers.size() > 0) {
for (IRenderer childPositionedRenderer : positionedRenderers) {
Rectangle fullBbox = occupiedArea.getBBox().clone();
// Use that value so that layout is independent of whether we are in the bottom of the page or in the top of the page
float layoutMinHeight = 1000;
fullBbox.moveDown(layoutMinHeight).setHeight(layoutMinHeight + fullBbox.getHeight());
LayoutArea parentArea = new LayoutArea(occupiedArea.getPageNumber(), occupiedArea.getBBox().clone());
applyPaddings(parentArea.getBBox(), paddings, true);
preparePositionedRendererAndAreaForLayout(childPositionedRenderer, fullBbox, parentArea.getBBox());
childPositionedRenderer.layout(new PositionedLayoutContext(new LayoutArea(occupiedArea.getPageNumber(), fullBbox), parentArea));
}
}
if (isPositioned) {
correctFixedLayout(layoutBox);
}
applyPaddings(occupiedArea.getBBox(), paddings, true);
applyBorderBox(occupiedArea.getBBox(), borders, true);
applyMargins(occupiedArea.getBBox(), true);
applyAbsolutePositionIfNeeded(layoutContext);
if (rotation != null) {
applyRotationLayout(layoutContext.getArea().getBBox().clone());
if (isNotFittingLayoutArea(layoutContext.getArea())) {
if (isNotFittingWidth(layoutContext.getArea()) && !isNotFittingHeight(layoutContext.getArea())) {
LoggerFactory.getLogger(getClass())
.warn(MessageFormatUtil.format(LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA,
"It fits by height so it will be forced placed"));
} else if (!initialForcePlacementForRotationAdjustments) {
floatRendererAreas.retainAll(nonChildFloatingRendererAreas);
return new MinMaxWidthLayoutResult(LayoutResult.NOTHING, null, null, this, this);
}
}
}
applyVerticalAlignment();
FloatingHelper.removeFloatsAboveRendererBottom(floatRendererAreas, this);
if (layoutResult != LayoutResult.NOTHING) {
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
return new LayoutResult(layoutResult, editedArea, splitRenderer, overflowRenderer, causeOfNothing);
} else {
if (positionedRenderers.size() > 0) {
overflowRenderer.positionedRenderers = new ArrayList<>(positionedRenderers);
}
floatRendererAreas.retainAll(nonChildFloatingRendererAreas);
return new LayoutResult(LayoutResult.NOTHING, null, null, overflowRenderer, causeOfNothing);
}
}
@Override
public void draw(DrawContext drawContext) {
Logger logger = LoggerFactory.getLogger(BlockRenderer.class);
if (occupiedArea == null) {
logger.error(MessageFormatUtil.format(IoLogMessageConstant.OCCUPIED_AREA_HAS_NOT_BEEN_INITIALIZED,
"Drawing won't be performed."));
return;
}
boolean isTagged = drawContext.isTaggingEnabled();
LayoutTaggingHelper taggingHelper = null;
if (isTagged) {
taggingHelper = this.getProperty(Property.TAGGING_HELPER);
if (taggingHelper == null) {
isTagged = false;
} else {
TagTreePointer tagPointer = taggingHelper.useAutoTaggingPointerAndRememberItsPosition(this);
if (taggingHelper.createTag(this, tagPointer)) {
tagPointer.getProperties()
.addAttributes(0, AccessibleAttributesApplier.getListAttributes(this, tagPointer))
.addAttributes(0, AccessibleAttributesApplier.getTableAttributes(this, tagPointer))
.addAttributes(0, AccessibleAttributesApplier.getLayoutAttributes(this, tagPointer));
}
}
}
beginTransformationIfApplied(drawContext.getCanvas());
applyDestinationsAndAnnotation(drawContext);
boolean isRelativePosition = isRelativePosition();
if (isRelativePosition) {
applyRelativePositioningTranslation(false);
}
beginElementOpacityApplying(drawContext);
beginRotationIfApplied(drawContext.getCanvas());
boolean overflowXHidden = isOverflowProperty(OverflowPropertyValue.HIDDEN, Property.OVERFLOW_X);
boolean overflowYHidden = isOverflowProperty(OverflowPropertyValue.HIDDEN, Property.OVERFLOW_Y);
boolean processOverflow = overflowXHidden || overflowYHidden;
drawBackground(drawContext);
drawBorder(drawContext);
addMarkedContent(drawContext, true);
if (processOverflow) {
drawContext.getCanvas().saveState();
int pageNumber = occupiedArea.getPageNumber();
Rectangle clippedArea;
if (pageNumber < 1 || pageNumber > drawContext.getDocument().getNumberOfPages()) {
clippedArea = new Rectangle(-INF / 2 , -INF / 2, INF, INF);
} else {
PdfPage page = drawContext.getDocument().getPage(pageNumber);
// TODO DEVSIX-1655 This check is necessary because, in some cases, our renderer's hierarchy may contain
// a renderer from the different page that was already flushed
if (page.isFlushed()) {
logger.error(MessageFormatUtil.format(
IoLogMessageConstant.PAGE_WAS_FLUSHED_ACTION_WILL_NOT_BE_PERFORMED,
"area clipping"));
clippedArea = new Rectangle(-INF / 2 , -INF / 2, INF, INF);
} else {
clippedArea = page.getPageSize();
}
}
Rectangle area = getBorderAreaBBox();
if (overflowXHidden) {
clippedArea.setX(area.getX()).setWidth(area.getWidth());
}
if (overflowYHidden) {
clippedArea.setY(area.getY()).setHeight(area.getHeight());
}
drawContext.getCanvas().rectangle(clippedArea).clip().endPath();
}
drawChildren(drawContext);
addMarkedContent(drawContext, false);
drawPositionedChildren(drawContext);
if (processOverflow) {
drawContext.getCanvas().restoreState();
}
endRotationIfApplied(drawContext.getCanvas());
endElementOpacityApplying(drawContext);
if (isRelativePosition) {
applyRelativePositioningTranslation(true);
}
if (isTagged) {
if (isLastRendererForModelElement) {
taggingHelper.finishTaggingHint(this);
}
taggingHelper.restoreAutoTaggingPointerPosition(this);
}
flushed = true;
endTransformationIfApplied(drawContext.getCanvas());
}
@Override
public Rectangle getOccupiedAreaBBox() {
Rectangle bBox = occupiedArea.getBBox().clone();
Float rotationAngle = this.getProperty(Property.ROTATION_ANGLE);
if (rotationAngle != null) {
if (!hasOwnProperty(Property.ROTATION_INITIAL_WIDTH) || !hasOwnProperty(Property.ROTATION_INITIAL_HEIGHT)) {
Logger logger = LoggerFactory.getLogger(BlockRenderer.class);
logger.error(
MessageFormatUtil.format(IoLogMessageConstant.ROTATION_WAS_NOT_CORRECTLY_PROCESSED_FOR_RENDERER,
getClass().getSimpleName()));
} else {
bBox.setWidth((float) this.getPropertyAsFloat(Property.ROTATION_INITIAL_WIDTH));
bBox.setHeight((float) this.getPropertyAsFloat(Property.ROTATION_INITIAL_HEIGHT));
}
}
return bBox;
}
/**
* Creates a split renderer.
*
* @param layoutResult the result of content layouting
*
* @return a new {@link AbstractRenderer} instance
*/
protected AbstractRenderer createSplitRenderer(int layoutResult) {
AbstractRenderer splitRenderer = (AbstractRenderer) getNextRenderer();
splitRenderer.parent = parent;
splitRenderer.modelElement = modelElement;
splitRenderer.occupiedArea = occupiedArea;
splitRenderer.isLastRendererForModelElement = false;
splitRenderer.addAllProperties(getOwnProperties());
return splitRenderer;
}
/**
* Creates an overflow renderer.
*
* @param layoutResult the result of content layouting
*
* @return a new {@link AbstractRenderer} instance
*/
protected AbstractRenderer createOverflowRenderer(int layoutResult) {
AbstractRenderer overflowRenderer = (AbstractRenderer) getNextRenderer();
overflowRenderer.parent = parent;
overflowRenderer.modelElement = modelElement;
overflowRenderer.addAllProperties(getOwnProperties());
return overflowRenderer;
}
void recalculateOccupiedAreaAfterChildLayout(Rectangle resultBBox, Float blockMaxHeight) {
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), resultBBox));
}
MarginsCollapseInfo startChildMarginsHandling(IRenderer childRenderer,
Rectangle layoutBox, MarginsCollapseHandler marginsCollapseHandler) {
return marginsCollapseHandler.startChildMarginsHandling(childRenderer, layoutBox);
}
Rectangle recalculateLayoutBoxBeforeChildLayout(Rectangle layoutBox,
IRenderer childRenderer, Rectangle initialLayoutBox) {
return layoutBox;
}
AbstractRenderer[] createSplitAndOverflowRenderers(int childPos, int layoutStatus, LayoutResult childResult,
Map waitingFloatsSplitRenderers,
List waitingOverflowFloatRenderers) {
AbstractRenderer splitRenderer = createSplitRenderer(layoutStatus);
splitRenderer.childRenderers = new ArrayList<>(childRenderers.subList(0, childPos));
if (childResult.getStatus() == LayoutResult.PARTIAL && childResult.getSplitRenderer() != null) {
splitRenderer.childRenderers.add(childResult.getSplitRenderer());
}
replaceSplitRendererKidFloats(waitingFloatsSplitRenderers, splitRenderer);
for (IRenderer renderer : splitRenderer.childRenderers) {
renderer.setParent(splitRenderer);
}
AbstractRenderer overflowRenderer = createOverflowRenderer(layoutStatus);
overflowRenderer.childRenderers.addAll(waitingOverflowFloatRenderers);
if (childResult.getOverflowRenderer() != null) {
overflowRenderer.childRenderers.add(childResult.getOverflowRenderer());
}
overflowRenderer.childRenderers.addAll(childRenderers.subList(childPos + 1, childRenderers.size()));
if (childResult.getStatus() == LayoutResult.PARTIAL) {
// Apply forced placement only on split renderer
overflowRenderer.deleteOwnProperty(Property.FORCED_PLACEMENT);
}
return new AbstractRenderer[]{splitRenderer, overflowRenderer};
}
/**
* This method applies vertical alignment for the occupied area
* of the renderer and its children renderers.
*/
protected void applyVerticalAlignment() {
VerticalAlignment verticalAlignment = this.getProperty(Property.VERTICAL_ALIGNMENT);
if (verticalAlignment == null || verticalAlignment == VerticalAlignment.TOP || childRenderers.isEmpty()) {
return;
}
float lowestChildBottom = Float.MAX_VALUE;
if (FloatingHelper.isRendererFloating(this) || this instanceof CellRenderer) {
// include floats in vertical alignment
for (IRenderer child : childRenderers) {
if (child.getOccupiedArea() != null &&
child.getOccupiedArea().getBBox().getBottom() < lowestChildBottom) {
lowestChildBottom = child.getOccupiedArea().getBBox().getBottom();
}
}
} else {
int lastChildIndex = childRenderers.size() - 1;
while (lastChildIndex >= 0) {
IRenderer child = childRenderers.get(lastChildIndex--);
if (!FloatingHelper.isRendererFloating(child) && child.getOccupiedArea() != null) {
lowestChildBottom = child.getOccupiedArea().getBBox().getBottom();
break;
}
}
}
if (lowestChildBottom == Float.MAX_VALUE) {
return;
}
float deltaY = lowestChildBottom - getInnerAreaBBox().getY();
if (deltaY < 0) {
return;
}
switch (verticalAlignment) {
case BOTTOM:
for (IRenderer child : childRenderers) {
child.move(0, -deltaY);
}
break;
case MIDDLE:
for (IRenderer child : childRenderers) {
child.move(0, -deltaY / 2);
}
break;
}
}
/**
* This method rotates content of the renderer and
* calculates correct occupied area for the rotated element.
*
* @param layoutBox a {@link Rectangle}
*/
protected void applyRotationLayout(Rectangle layoutBox) {
float angle = (float) this.getPropertyAsFloat(Property.ROTATION_ANGLE);
float x = occupiedArea.getBBox().getX();
float y = occupiedArea.getBBox().getY();
float height = occupiedArea.getBBox().getHeight();
float width = occupiedArea.getBBox().getWidth();
setProperty(Property.ROTATION_INITIAL_WIDTH, width);
setProperty(Property.ROTATION_INITIAL_HEIGHT, height);
AffineTransform rotationTransform = new AffineTransform();
// here we calculate and set the actual occupied area of the rotated content
if (isPositioned()) {
Float rotationPointX = this.getPropertyAsFloat(Property.ROTATION_POINT_X);
Float rotationPointY = this.getPropertyAsFloat(Property.ROTATION_POINT_Y);
if (rotationPointX == null || rotationPointY == null) {
// if rotation point was not specified, the most bottom-left point is used
rotationPointX = x;
rotationPointY = y;
}
// transforms apply from bottom to top
// move point back at place
rotationTransform.translate((float) rotationPointX, (float) rotationPointY);
// rotate
rotationTransform.rotate(angle);
// move rotation point to origin
rotationTransform.translate((float) -rotationPointX, (float) -rotationPointY);
List rotatedPoints = transformPoints(rectangleToPointsList(occupiedArea.getBBox()), rotationTransform);
Rectangle newBBox = calculateBBox(rotatedPoints);
// make occupied area be of size and position of actual content
occupiedArea.getBBox().setWidth(newBBox.getWidth());
occupiedArea.getBBox().setHeight(newBBox.getHeight());
float occupiedAreaShiftX = newBBox.getX() - x;
float occupiedAreaShiftY = newBBox.getY() - y;
move(occupiedAreaShiftX, occupiedAreaShiftY);
} else {
rotationTransform = AffineTransform.getRotateInstance(angle);
List rotatedPoints = transformPoints(rectangleToPointsList(occupiedArea.getBBox()), rotationTransform);
float[] shift = calculateShiftToPositionBBoxOfPointsAt(x, y + height, rotatedPoints);
for (Point point : rotatedPoints) {
point.setLocation(point.getX() + shift[0], point.getY() + shift[1]);
}
Rectangle newBBox = calculateBBox(rotatedPoints);
occupiedArea.getBBox().setWidth(newBBox.getWidth());
occupiedArea.getBBox().setHeight(newBBox.getHeight());
float heightDiff = height - newBBox.getHeight();
move(0, heightDiff);
}
}
/**
* This method creates {@link AffineTransform} instance that could be used
* to rotate content inside the occupied area. Be aware that it should be used only after
* layout rendering is finished and correct occupied area for the rotated element is calculated.
*
* @return {@link AffineTransform} that rotates the content and places it inside occupied area.
*/
protected AffineTransform createRotationTransformInsideOccupiedArea() {
Float angle = this.getProperty(Property.ROTATION_ANGLE);
AffineTransform rotationTransform = AffineTransform.getRotateInstance((float) angle);
Rectangle contentBox = this.getOccupiedAreaBBox();
List rotatedContentBoxPoints = transformPoints(rectangleToPointsList(contentBox), rotationTransform);
// Occupied area for rotated elements is already calculated on layout in such way to enclose rotated content;
// therefore we can simply rotate content as is and then shift it to the occupied area.
float[] shift = calculateShiftToPositionBBoxOfPointsAt(occupiedArea.getBBox().getLeft(), occupiedArea.getBBox().getTop(), rotatedContentBoxPoints);
rotationTransform.preConcatenate(AffineTransform.getTranslateInstance(shift[0], shift[1]));
return rotationTransform;
}
/**
* This method starts rotation for the renderer if rotation angle property is specified.
*
* @param canvas the {@link PdfCanvas} to draw on
*/
protected void beginRotationIfApplied(PdfCanvas canvas) {
Float angle = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
if (angle != null) {
if (!hasOwnProperty(Property.ROTATION_INITIAL_HEIGHT)) {
Logger logger = LoggerFactory.getLogger(BlockRenderer.class);
logger.error(
MessageFormatUtil.format(IoLogMessageConstant.ROTATION_WAS_NOT_CORRECTLY_PROCESSED_FOR_RENDERER,
getClass().getSimpleName()));
} else {
AffineTransform transform = createRotationTransformInsideOccupiedArea();
canvas.saveState().concatMatrix(transform);
}
}
}
/**
* This method ends rotation for the renderer if applied.
*
* @param canvas the {@link PdfCanvas} to draw on
*/
protected void endRotationIfApplied(PdfCanvas canvas) {
Float angle = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
if (angle != null && hasOwnProperty(Property.ROTATION_INITIAL_HEIGHT)) {
canvas.restoreState();
}
}
boolean stopLayoutingChildrenIfChildResultNotFull(LayoutResult returnResult) {
return true;
}
LayoutResult processNotFullChildResult(LayoutContext layoutContext,
Map waitingFloatsSplitRenderers,
List waitingOverflowFloatRenderers, boolean wasHeightClipped,
List floatRendererAreas, boolean marginsCollapsingEnabled,
float clearHeightCorrection, Border[] borders, UnitValue[] paddings,
List areas, int currentAreaPos, Rectangle layoutBox,
Set nonChildFloatingRendererAreas, IRenderer causeOfNothing,
boolean anythingPlaced, int childPos, LayoutResult result) {
if (result.getStatus() == LayoutResult.PARTIAL) {
if (currentAreaPos + 1 == areas.size()) {
AbstractRenderer[] splitAndOverflowRenderers = createSplitAndOverflowRenderers(childPos,
LayoutResult.PARTIAL, result, waitingFloatsSplitRenderers, waitingOverflowFloatRenderers);
AbstractRenderer splitRenderer = splitAndOverflowRenderers[0];
AbstractRenderer overflowRenderer = splitAndOverflowRenderers[1];
overflowRenderer.deleteOwnProperty(Property.FORCED_PLACEMENT);
updateHeightsOnSplit(wasHeightClipped, splitRenderer, overflowRenderer);
applyPaddings(occupiedArea.getBBox(), paddings, true);
applyBorderBox(occupiedArea.getBBox(), borders, true);
applyMargins(occupiedArea.getBBox(), true);
correctFixedLayout(layoutBox);
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
if (wasHeightClipped) {
return new LayoutResult(LayoutResult.FULL, editedArea, splitRenderer, null);
} else {
return new LayoutResult(LayoutResult.PARTIAL, editedArea, splitRenderer, overflowRenderer, causeOfNothing);
}
} else {
childRenderers.set(childPos, result.getSplitRenderer());
childRenderers.add(childPos + 1, result.getOverflowRenderer());
return null;
}
} else if (result.getStatus() == LayoutResult.NOTHING) {
boolean keepTogether = isKeepTogether(causeOfNothing);
int layoutResult = anythingPlaced && !keepTogether ? LayoutResult.PARTIAL : LayoutResult.NOTHING;
AbstractRenderer[] splitAndOverflowRenderers = createSplitAndOverflowRenderers(childPos, layoutResult,
result, waitingFloatsSplitRenderers, waitingOverflowFloatRenderers);
AbstractRenderer splitRenderer = splitAndOverflowRenderers[0];
AbstractRenderer overflowRenderer = splitAndOverflowRenderers[1];
if (isRelativePosition() && positionedRenderers.size() > 0) {
overflowRenderer.positionedRenderers = new ArrayList<>(positionedRenderers);
}
updateHeightsOnSplit(wasHeightClipped, splitRenderer, overflowRenderer);
if (keepTogether) {
splitRenderer = null;
overflowRenderer.childRenderers.clear();
overflowRenderer.childRenderers = new ArrayList<>(childRenderers);
}
correctFixedLayout(layoutBox);
applyPaddings(occupiedArea.getBBox(), paddings, true);
applyBorderBox(occupiedArea.getBBox(), borders, true);
applyMargins(occupiedArea.getBBox(), true);
applyAbsolutePositionIfNeeded(layoutContext);
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)) || wasHeightClipped) {
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
return new LayoutResult(LayoutResult.FULL, editedArea, splitRenderer, null, null);
} else {
if (layoutResult != LayoutResult.NOTHING) {
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
return new LayoutResult(layoutResult, editedArea, splitRenderer, overflowRenderer, null).setAreaBreak(result.getAreaBreak());
} else {
floatRendererAreas.retainAll(nonChildFloatingRendererAreas);
return new LayoutResult(layoutResult, null, null, overflowRenderer, result.getCauseOfNothing()).setAreaBreak(result.getAreaBreak());
}
}
}
return null;
}
void decreaseLayoutBoxAfterChildPlacement(Rectangle layoutBox, LayoutResult result, IRenderer childRenderer) {
layoutBox.setHeight(result.getOccupiedArea().getBBox().getY() - layoutBox.getY());
}
void correctFixedLayout(Rectangle layoutBox) {
if (isFixedLayout()) {
float y = (float) this.getPropertyAsFloat(Property.BOTTOM);
move(0, y - occupiedArea.getBBox().getY());
}
}
void applyWidth(Rectangle parentBBox, Float blockWidth, OverflowPropertyValue overflowX) {
// maxWidth has already taken in attention in blockWidth,
// therefore only `parentBBox > minWidth` needs to be checked.
Float rotation = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
if (blockWidth != null && (
blockWidth < parentBBox.getWidth() ||
isPositioned() ||
rotation != null ||
(!isOverflowFit(overflowX)))) {
parentBBox.setWidth((float) blockWidth);
} else {
Float minWidth = retrieveMinWidth(parentBBox.getWidth());
//Shall we check overflow-x here?
if (minWidth != null && minWidth > parentBBox.getWidth()) {
parentBBox.setWidth((float) minWidth);
}
}
}
boolean applyMaxHeight(Rectangle parentBBox, Float blockMaxHeight, MarginsCollapseHandler marginsCollapseHandler,
boolean isCellRenderer, boolean wasParentsHeightClipped, OverflowPropertyValue overflowY) {
if (null == blockMaxHeight || (blockMaxHeight >= parentBBox.getHeight() && (isOverflowFit(overflowY)))) {
return false;
}
boolean wasHeightClipped = false;
if (blockMaxHeight <= parentBBox.getHeight()) {
wasHeightClipped = true;
}
float heightDelta = parentBBox.getHeight() - (float) blockMaxHeight;
if (marginsCollapseHandler != null && !isCellRenderer) {
marginsCollapseHandler.processFixedHeightAdjustment(heightDelta);
}
parentBBox.moveUp(heightDelta).setHeight((float) blockMaxHeight);
return wasHeightClipped;
}
AbstractRenderer applyMinHeight(OverflowPropertyValue overflowY, Rectangle layoutBox) {
AbstractRenderer overflowRenderer = null;
Float blockMinHeight = retrieveMinHeight();
if (!Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)) && null != blockMinHeight && blockMinHeight > occupiedArea.getBBox().getHeight()) {
float blockBottom = occupiedArea.getBBox().getBottom() - ((float) blockMinHeight - occupiedArea.getBBox().getHeight());
if (isFixedLayout()) {
occupiedArea.getBBox().setY(blockBottom).setHeight((float) blockMinHeight);
} else {
// Because of float precision inaccuracy, iText can incorrectly calculate that the block of fixed height
// needs to be split. As a result, an empty block with a height equal to sum of paddings
// may appear on the next area. To prevent such situations epsilon is used.
if (isOverflowFit(overflowY) && blockBottom + EPS < layoutBox.getBottom()) {
float hDelta = occupiedArea.getBBox().getBottom() - layoutBox.getBottom();
occupiedArea.getBBox()
.increaseHeight(hDelta)
.setY(layoutBox.getBottom());
if (occupiedArea.getBBox().getHeight() < 0) {
occupiedArea.getBBox().setHeight(0);
}
this.isLastRendererForModelElement = false;
overflowRenderer = createOverflowRenderer(LayoutResult.PARTIAL);
overflowRenderer.updateMinHeight(UnitValue.createPointValue((float) blockMinHeight - occupiedArea.getBBox().getHeight()));
if (hasProperty(Property.HEIGHT)) {
overflowRenderer.updateHeight(UnitValue.createPointValue((float) retrieveHeight() - occupiedArea.getBBox().getHeight()));
}
} else {
occupiedArea.getBBox().setY(blockBottom).setHeight((float) blockMinHeight);
}
}
}
return overflowRenderer;
}
void fixOccupiedAreaIfOverflowedX(OverflowPropertyValue overflowX, Rectangle layoutBox) {
if (isOverflowFit(overflowX)) {
return;
}
if ((occupiedArea.getBBox().getWidth() > layoutBox.getWidth() || occupiedArea.getBBox().getLeft() < layoutBox.getLeft())) {
occupiedArea.getBBox().setX(layoutBox.getX()).setWidth(layoutBox.getWidth());
}
}
void fixOccupiedAreaIfOverflowedY(OverflowPropertyValue overflowY, Rectangle layoutBox) {
if (isOverflowFit(overflowY)) {
return;
}
if (occupiedArea.getBBox().getBottom() < layoutBox.getBottom()) {
float difference = layoutBox.getBottom() - occupiedArea.getBBox().getBottom();
occupiedArea.getBBox().moveUp(difference).decreaseHeight(difference);
}
}
/**
* {@inheritDoc}
*/
@Override
public MinMaxWidth getMinMaxWidth() {
MinMaxWidth minMaxWidth = new MinMaxWidth(calculateAdditionalWidth(this));
if (!setMinMaxWidthBasedOnFixedWidth(minMaxWidth)) {
Float minWidth = hasAbsoluteUnitValue(Property.MIN_WIDTH) ? retrieveMinWidth(0) : null;
Float maxWidth = hasAbsoluteUnitValue(Property.MAX_WIDTH) ? retrieveMaxWidth(0) : null;
if (minWidth == null || maxWidth == null) {
AbstractWidthHandler handler = new MaxMaxWidthHandler(minMaxWidth);
int epsilonNum = 0;
int curEpsNum = 0;
float previousFloatingChildWidth = 0;
for (IRenderer childRenderer : childRenderers) {
MinMaxWidth childMinMaxWidth;
childRenderer.setParent(this);
if (childRenderer instanceof AbstractRenderer) {
childMinMaxWidth = ((AbstractRenderer) childRenderer).getMinMaxWidth();
} else {
childMinMaxWidth = MinMaxWidthUtils.countDefaultMinMaxWidth(childRenderer);
}
handler.updateMaxChildWidth(childMinMaxWidth.getMaxWidth() + (FloatingHelper.isRendererFloating(childRenderer) ? previousFloatingChildWidth : 0));
handler.updateMinChildWidth(childMinMaxWidth.getMinWidth());
previousFloatingChildWidth = FloatingHelper.isRendererFloating(childRenderer) ? previousFloatingChildWidth + childMinMaxWidth.getMaxWidth() : 0;
if (FloatingHelper.isRendererFloating(childRenderer)) {
curEpsNum++;
} else {
epsilonNum = Math.max(epsilonNum, curEpsNum);
curEpsNum = 0;
}
}
epsilonNum = Math.max(epsilonNum, curEpsNum);
handler.minMaxWidth.setChildrenMaxWidth(handler.minMaxWidth.getChildrenMaxWidth() + epsilonNum * AbstractRenderer.EPS);
handler.minMaxWidth.setChildrenMinWidth(handler.minMaxWidth.getChildrenMinWidth() + epsilonNum * AbstractRenderer.EPS);
}
if (minWidth != null) {
minMaxWidth.setChildrenMinWidth((float) minWidth);
}
// if max-width was defined explicitly, it shouldn't be overwritten
if (maxWidth != null) {
minMaxWidth.setChildrenMaxWidth((float) maxWidth);
} else {
if (minMaxWidth.getChildrenMinWidth() > minMaxWidth.getChildrenMaxWidth()) {
minMaxWidth.setChildrenMaxWidth(minMaxWidth.getChildrenMinWidth());
}
}
}
if (this.getPropertyAsFloat(Property.ROTATION_ANGLE) != null) {
return RotationUtils.countRotationMinMaxWidth(minMaxWidth, this);
}
return minMaxWidth;
}
void handleForcedPlacement(boolean anythingPlaced) {
// We placed something meaning that we don't need this property anymore while processing other children
// to do not force place them
if (anythingPlaced && hasOwnProperty(Property.FORCED_PLACEMENT)) {
deleteOwnProperty(Property.FORCED_PLACEMENT);
}
}
private void replaceSplitRendererKidFloats(Map waitingFloatsSplitRenderers, IRenderer splitRenderer) {
for (Map.Entry waitingSplitRenderer : waitingFloatsSplitRenderers.entrySet()) {
if (waitingSplitRenderer.getValue() != null) {
splitRenderer.getChildRenderers().set(waitingSplitRenderer.getKey(), waitingSplitRenderer.getValue());
} else {
splitRenderer.getChildRenderers().set((int) waitingSplitRenderer.getKey(), null);
}
}
for (int i = splitRenderer.getChildRenderers().size() - 1; i >= 0; --i) {
if (splitRenderer.getChildRenderers().get(i) == null) {
splitRenderer.getChildRenderers().remove(i);
}
}
}
private void addMarkedContent(DrawContext drawContext, boolean isBegin) {
if (Boolean.TRUE.equals(this.getProperty(Property.ADD_MARKED_CONTENT_TEXT))) {
PdfCanvas canvas = drawContext.getCanvas();
if (isBegin) {
canvas.beginVariableText().saveState().endPath();
} else {
canvas.restoreState().endVariableText();
}
}
}
private List clipPolygon(List points, Point clipLineBeg, Point clipLineEnd) {
List filteredPoints = new ArrayList<>();
boolean prevOnRightSide = false;
Point filteringPoint = points.get(0);
if (checkPointSide(filteringPoint, clipLineBeg, clipLineEnd) >= 0) {
filteredPoints.add(filteringPoint);
prevOnRightSide = true;
}
Point prevPoint = filteringPoint;
for (int i = 1; i < points.size() + 1; ++i) {
filteringPoint = points.get(i % points.size());
if (checkPointSide(filteringPoint, clipLineBeg, clipLineEnd) >= 0) {
if (!prevOnRightSide) {
filteredPoints.add(getIntersectionPoint(prevPoint, filteringPoint, clipLineBeg, clipLineEnd));
}
filteredPoints.add(filteringPoint);
prevOnRightSide = true;
} else if (prevOnRightSide) {
filteredPoints.add(getIntersectionPoint(prevPoint, filteringPoint, clipLineBeg, clipLineEnd));
}
prevPoint = filteringPoint;
}
return filteredPoints;
}
private int checkPointSide(Point filteredPoint, Point clipLineBeg, Point clipLineEnd) {
double x1, x2, y1, y2;
x1 = filteredPoint.getX() - clipLineBeg.getX();
y2 = clipLineEnd.getY() - clipLineBeg.getY();
x2 = clipLineEnd.getX() - clipLineBeg.getX();
y1 = filteredPoint.getY() - clipLineBeg.getY();
double sgn = x1 * y2 - x2 * y1;
if (Math.abs(sgn) < 0.001) return 0;
if (sgn > 0) return 1;
if (sgn < 0) return -1;
return 0;
}
private Point getIntersectionPoint(Point lineBeg, Point lineEnd, Point clipLineBeg, Point clipLineEnd) {
double A1 = lineBeg.getY() - lineEnd.getY(), A2 = clipLineBeg.getY() - clipLineEnd.getY();
double B1 = lineEnd.getX() - lineBeg.getX(), B2 = clipLineEnd.getX() - clipLineBeg.getX();
double C1 = lineBeg.getX() * lineEnd.getY() - lineBeg.getY() * lineEnd.getX();
double C2 = clipLineBeg.getX() * clipLineEnd.getY() - clipLineBeg.getY() * clipLineEnd.getX();
double M = B1 * A2 - B2 * A1;
return new Point((B2 * C1 - B1 * C2) / M, (C2 * A1 - C1 * A2) / M);
}
}