Skip to content

Commit b8ddb83

Browse files
committed
Improve absolute positioning support
DEVSIX-1381
1 parent 21d29ba commit b8ddb83

File tree

6 files changed

+158
-72
lines changed

6 files changed

+158
-72
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.itextpdf.layout.layout;
2+
3+
public class PositionedLayoutContext extends LayoutContext {
4+
5+
private LayoutArea parentOccupiedArea;
6+
7+
public PositionedLayoutContext(LayoutArea area, LayoutArea parentOccupiedArea) {
8+
super(area);
9+
this.parentOccupiedArea = parentOccupiedArea;
10+
}
11+
12+
public LayoutArea getParentOccupiedArea() {
13+
return parentOccupiedArea;
14+
}
15+
16+
}

layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java

Lines changed: 97 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.layout.renderer;
4545

4646
import com.itextpdf.io.LogMessageConstant;
47+
import com.itextpdf.io.util.MessageFormatUtil;
4748
import com.itextpdf.io.util.NumberUtil;
4849
import com.itextpdf.kernel.color.Color;
4950
import com.itextpdf.kernel.font.PdfFont;
@@ -72,19 +73,21 @@ This file is part of the iText (R) project.
7273
import com.itextpdf.layout.font.FontFamilySplitter;
7374
import com.itextpdf.layout.font.FontProvider;
7475
import com.itextpdf.layout.layout.LayoutArea;
76+
import com.itextpdf.layout.layout.LayoutContext;
7577
import com.itextpdf.layout.layout.LayoutPosition;
78+
import com.itextpdf.layout.layout.PositionedLayoutContext;
7679
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
7780
import com.itextpdf.layout.minmaxwidth.MinMaxWidthUtils;
7881
import com.itextpdf.layout.property.Background;
7982
import com.itextpdf.layout.property.BackgroundImage;
83+
import com.itextpdf.layout.property.BaseDirection;
8084
import com.itextpdf.layout.property.HorizontalAlignment;
8185
import com.itextpdf.layout.property.Property;
8286
import com.itextpdf.layout.property.TransparentColor;
8387
import com.itextpdf.layout.property.UnitValue;
8488
import org.slf4j.Logger;
8589
import org.slf4j.LoggerFactory;
8690

87-
import com.itextpdf.io.util.MessageFormatUtil;
8891
import java.util.ArrayList;
8992
import java.util.Arrays;
9093
import java.util.Collections;
@@ -871,54 +874,39 @@ protected Rectangle applyBorderBox(Rectangle rect, Border[] borders, boolean rev
871874
return rect.<Rectangle>applyMargins(topWidth, rightWidth, bottomWidth, leftWidth, reverse);
872875
}
873876

874-
protected void applyAbsolutePosition(Rectangle rect) {
877+
protected void applyAbsolutePosition(Rectangle parentRect) {
875878
Float top = this.getPropertyAsFloat(Property.TOP);
876879
Float bottom = this.getPropertyAsFloat(Property.BOTTOM);
877880
Float left = this.getPropertyAsFloat(Property.LEFT);
878881
Float right = this.getPropertyAsFloat(Property.RIGHT);
879882

880-
float initialHeight = rect.getHeight();
881-
float initialWidth = rect.getWidth();
882-
883-
Float minHeight = this.getPropertyAsFloat(Property.MIN_HEIGHT);
884-
885-
if (minHeight != null && rect.getHeight() < (float)minHeight) {
886-
float difference = (float)minHeight - rect.getHeight();
887-
rect.moveDown(difference).setHeight(rect.getHeight() + difference);
883+
if (left == null && right == null && BaseDirection.RIGHT_TO_LEFT.equals(this.<BaseDirection>getProperty(Property.BASE_DIRECTION))) {
884+
right = 0f;
888885
}
889886

890-
if (top != null) {
891-
rect.setHeight(rect.getHeight() - (float)top);
892-
}
893-
if (left != null) {
894-
rect.setX(rect.getX() + (float)left).setWidth(rect.getWidth() - (float)left);
887+
if (top == null && bottom == null) {
888+
top = 0f;
895889
}
896890

897-
if (right != null) {
898-
UnitValue width = this.<UnitValue>getProperty(Property.WIDTH);
899-
if (left == null && width != null) {
900-
float widthValue = width.isPointValue() ? width.getValue() : (width.getValue() * initialWidth);
901-
float placeLeft = rect.getWidth() - widthValue;
902-
if (placeLeft > 0) {
903-
float computedRight = Math.min(placeLeft, (float)right);
904-
rect.setX(rect.getX() + rect.getWidth() - computedRight - widthValue);
905-
}
906-
} else if (width == null) {
907-
rect.setWidth(rect.getWidth() - (float)right);
891+
try {
892+
if (right != null) {
893+
move(parentRect.getRight() - (float) right - occupiedArea.getBBox().getRight(), 0);
908894
}
909-
}
910895

911-
if (bottom != null) {
912-
if (minHeight != null) {
913-
rect.setHeight((float)minHeight + (float)bottom);
914-
} else {
915-
float minHeightValue = rect.getHeight() - (float)bottom;
916-
Float currentMaxHeight = this.getPropertyAsFloat(Property.MAX_HEIGHT);
917-
if (currentMaxHeight != null) {
918-
minHeightValue = Math.min(minHeightValue, (float)currentMaxHeight);
919-
}
920-
setProperty(Property.MIN_HEIGHT, minHeightValue);
896+
if (left != null) {
897+
move(parentRect.getLeft() + (float) left - occupiedArea.getBBox().getLeft(), 0);
921898
}
899+
900+
if (top != null) {
901+
move(0, parentRect.getTop() - (float) top - occupiedArea.getBBox().getTop());
902+
}
903+
904+
if (bottom != null) {
905+
move(0, parentRect.getBottom() + (float) bottom - occupiedArea.getBBox().getBottom());
906+
}
907+
} catch (NullPointerException exc) {
908+
Logger logger = LoggerFactory.getLogger(AbstractRenderer.class);
909+
logger.error(MessageFormatUtil.format(LogMessageConstant.OCCUPIED_AREA_HAS_NOT_BEEN_INITIALIZED, "Absolute positioning might be applied incorrectly."));
922910
}
923911
}
924912

@@ -1269,6 +1257,31 @@ static boolean noAbsolutePositionInfo(IRenderer renderer) {
12691257
return !renderer.hasProperty(Property.TOP) && !renderer.hasProperty(Property.BOTTOM) && !renderer.hasProperty(Property.LEFT) && !renderer.hasProperty(Property.RIGHT);
12701258
}
12711259

1260+
static Float getPropertyAsFloat(IRenderer renderer, int property) {
1261+
return NumberUtil.asFloat(renderer.<Object>getProperty(property));
1262+
}
1263+
1264+
static void applyGeneratedAccessibleAttributes(TagTreePointer tagPointer, PdfDictionary attributes) {
1265+
if (attributes == null) {
1266+
return;
1267+
}
1268+
1269+
// TODO if taggingPointer.getProperties will always write directly to struct elem, use it instead (add addAttributes overload with index)
1270+
PdfStructElem structElem = tagPointer.getDocument().getTagStructureContext().getPointerStructElem(tagPointer);
1271+
PdfObject structElemAttr = structElem.getAttributes(false);
1272+
if (structElemAttr == null || !structElemAttr.isDictionary() && !structElemAttr.isArray()) {
1273+
structElem.setAttributes(attributes);
1274+
} else if (structElemAttr.isDictionary()) {
1275+
PdfArray attrArr = new PdfArray();
1276+
attrArr.add(attributes);
1277+
attrArr.add(structElemAttr);
1278+
structElem.setAttributes(attrArr);
1279+
} else { // isArray
1280+
PdfArray attrArr = (PdfArray) structElemAttr;
1281+
attrArr.add(0, attributes);
1282+
}
1283+
}
1284+
12721285
void shrinkOccupiedAreaForAbsolutePosition() {
12731286
// In case of absolute positioning and not specified left, right, width values, the parent box is shrunk to fit
12741287
// the children. It does not occupy all the available width if it does not need to.
@@ -1327,24 +1340,55 @@ PdfFont resolveFirstPdfFont(String font, FontProvider provider, FontCharacterist
13271340
return provider.getPdfFont(provider.getFontSelector(FontFamilySplitter.splitFontFamily(font), fc).bestMatch());
13281341
}
13291342

1330-
static void applyGeneratedAccessibleAttributes(TagTreePointer tagPointer, PdfDictionary attributes) {
1331-
if (attributes == null) {
1332-
return;
1343+
void applyAbsolutePositionIfNeeded(LayoutContext layoutContext) {
1344+
if (isAbsolutePosition()) {
1345+
applyAbsolutePosition(layoutContext instanceof PositionedLayoutContext ? ((PositionedLayoutContext) layoutContext).getParentOccupiedArea().getBBox() : layoutContext.getArea().getBBox());
13331346
}
1347+
}
13341348

1335-
// TODO if taggingPointer.getProperties will always write directly to struct elem, use it instead (add addAttributes overload with index)
1336-
PdfStructElem structElem = tagPointer.getDocument().getTagStructureContext().getPointerStructElem(tagPointer);
1337-
PdfObject structElemAttr = structElem.getAttributes(false);
1338-
if (structElemAttr == null || !structElemAttr.isDictionary() && !structElemAttr.isArray()) {
1339-
structElem.setAttributes(attributes);
1340-
} else if (structElemAttr.isDictionary()) {
1341-
PdfArray attrArr = new PdfArray();
1342-
attrArr.add(attributes);
1343-
attrArr.add(structElemAttr);
1344-
structElem.setAttributes(attrArr);
1345-
} else { // isArray
1346-
PdfArray attrArr = (PdfArray) structElemAttr;
1347-
attrArr.add(0, attributes);
1349+
void preparePositionedRendererAndAreaForLayout(IRenderer childPositionedRenderer, Rectangle fullBbox, Rectangle parentBbox) {
1350+
Float left = getPropertyAsFloat(childPositionedRenderer, Property.LEFT);
1351+
Float right = getPropertyAsFloat(childPositionedRenderer, Property.RIGHT);
1352+
Float top = getPropertyAsFloat(childPositionedRenderer, Property.TOP);
1353+
Float bottom = getPropertyAsFloat(childPositionedRenderer, Property.BOTTOM);
1354+
childPositionedRenderer.setParent(this);
1355+
adjustPositionedRendererLayoutBoxWidth(childPositionedRenderer, fullBbox, left, right);
1356+
1357+
if (Integer.valueOf(LayoutPosition.ABSOLUTE).equals(childPositionedRenderer.<Integer>getProperty(Property.POSITION))) {
1358+
updateMinHeightForAbsolutelyPositionedRenderer(childPositionedRenderer, parentBbox, top, bottom);
1359+
}
1360+
}
1361+
1362+
private void updateMinHeightForAbsolutelyPositionedRenderer(IRenderer renderer, Rectangle parentRendererBox, Float top, Float bottom) {
1363+
if (top != null && bottom != null && !renderer.hasProperty(Property.HEIGHT)) {
1364+
Float currentMaxHeight = getPropertyAsFloat(renderer, Property.MAX_HEIGHT);
1365+
Float currentMinHeight = getPropertyAsFloat(renderer, Property.MIN_HEIGHT);
1366+
float resolvedMinHeight = Math.max(0, parentRendererBox.getTop() - (float) top - parentRendererBox.getBottom() - (float) bottom);
1367+
if (currentMinHeight != null) {
1368+
resolvedMinHeight = Math.max(resolvedMinHeight, (float) currentMinHeight);
1369+
}
1370+
if (currentMaxHeight != null) {
1371+
resolvedMinHeight = Math.min(resolvedMinHeight, (float) currentMaxHeight);
1372+
}
1373+
renderer.setProperty(Property.MIN_HEIGHT, resolvedMinHeight);
1374+
}
1375+
}
1376+
1377+
private void adjustPositionedRendererLayoutBoxWidth(IRenderer renderer, Rectangle fullBbox, Float left, Float right) {
1378+
if (left != null) {
1379+
fullBbox.setWidth(fullBbox.getWidth() - (float)left).setX(fullBbox.getX() + (float)left);
1380+
}
1381+
if (right != null) {
1382+
fullBbox.setWidth(fullBbox.getWidth() - (float)right);
1383+
}
1384+
1385+
if (left == null && right == null && !renderer.hasProperty(Property.WIDTH)) {
1386+
// Other, non-block renderers won't occupy full width anyway
1387+
MinMaxWidth minMaxWidth = renderer instanceof BlockRenderer ? ((BlockRenderer) renderer).getMinMaxWidth(MinMaxWidthUtils.getMax()) : null;
1388+
if (minMaxWidth != null && minMaxWidth.getMaxWidth() < fullBbox.getWidth()) {
1389+
fullBbox.setWidth(minMaxWidth.getMaxWidth() + AbstractRenderer.EPS);
1390+
}
13481391
}
13491392
}
1393+
13501394
}

layout/src/main/java/com/itextpdf/layout/renderer/BlockRenderer.java

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.layout.renderer;
4545

4646
import com.itextpdf.io.LogMessageConstant;
47+
import com.itextpdf.io.util.MessageFormatUtil;
4748
import com.itextpdf.kernel.geom.AffineTransform;
4849
import com.itextpdf.kernel.geom.Point;
4950
import com.itextpdf.kernel.geom.Rectangle;
@@ -60,15 +61,19 @@ This file is part of the iText (R) project.
6061
import com.itextpdf.layout.layout.LayoutContext;
6162
import com.itextpdf.layout.layout.LayoutResult;
6263
import com.itextpdf.layout.layout.MinMaxWidthLayoutResult;
64+
import com.itextpdf.layout.layout.PositionedLayoutContext;
6365
import com.itextpdf.layout.margincollapse.MarginsCollapseHandler;
6466
import com.itextpdf.layout.margincollapse.MarginsCollapseInfo;
6567
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
6668
import com.itextpdf.layout.minmaxwidth.MinMaxWidthUtils;
67-
import com.itextpdf.layout.property.*;
69+
import com.itextpdf.layout.property.AreaBreakType;
70+
import com.itextpdf.layout.property.FloatPropertyValue;
71+
import com.itextpdf.layout.property.Property;
72+
import com.itextpdf.layout.property.UnitValue;
73+
import com.itextpdf.layout.property.VerticalAlignment;
6874
import org.slf4j.Logger;
6975
import org.slf4j.LoggerFactory;
7076

71-
import com.itextpdf.io.util.MessageFormatUtil;
7277
import java.util.ArrayList;
7378
import java.util.Collections;
7479
import java.util.HashMap;
@@ -324,6 +329,8 @@ public LayoutResult layout(LayoutContext layoutContext) {
324329
applyMargins(occupiedArea.getBBox(), true);
325330
//splitRenderer.occupiedArea = occupiedArea.clone();
326331

332+
applyAbsolutePositionIfNeeded(layoutContext);
333+
327334
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
328335

329336
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)) || wasHeightClipped) {
@@ -343,6 +350,8 @@ public LayoutResult layout(LayoutContext layoutContext) {
343350
if (result.getOccupiedArea() != null) {
344351
if (!FloatingHelper.isRendererFloating(childRenderer)) { // this check is needed only if margins collapsing is enabled
345352
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), result.getOccupiedArea().getBBox()));
353+
} else if (isAbsolutePosition() && childRenderer.getOccupiedArea() != null) {
354+
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), childRenderer.getOccupiedArea().getBBox()));
346355
}
347356
}
348357
if (marginsCollapsingEnabled) {
@@ -400,21 +409,31 @@ public LayoutResult layout(LayoutContext layoutContext) {
400409
}
401410
}
402411

412+
if (positionedRenderers.size() > 0) {
413+
for (IRenderer childPositionedRenderer : positionedRenderers) {
414+
Rectangle fullBbox = occupiedArea.getBBox().clone();
415+
416+
// 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
417+
float layoutMinHeight = 1000;
418+
fullBbox.moveDown(layoutMinHeight).setHeight(layoutMinHeight + fullBbox.getHeight());
419+
LayoutArea parentArea = new LayoutArea(occupiedArea.getPageNumber(), occupiedArea.getBBox().clone());
420+
applyPaddings(parentArea.getBBox(), paddings, true);
421+
422+
preparePositionedRendererAndAreaForLayout(childPositionedRenderer, fullBbox, parentArea.getBBox());
423+
childPositionedRenderer.layout(new PositionedLayoutContext(new LayoutArea(occupiedArea.getPageNumber(), fullBbox), parentArea));
424+
}
425+
}
426+
403427
if (isPositioned) {
404428
correctPositionedLayout(layoutBox);
405429
}
406430

407431
applyPaddings(occupiedArea.getBBox(), paddings, true);
408432
applyBorderBox(occupiedArea.getBBox(), borders, true);
409-
if (positionedRenderers.size() > 0) {
410-
LayoutArea area = new LayoutArea(occupiedArea.getPageNumber(), occupiedArea.getBBox().clone());
411-
applyBorderBox(area.getBBox(), false);
412-
for (IRenderer childPositionedRenderer : positionedRenderers) {
413-
childPositionedRenderer.setParent(this).layout(new LayoutContext(area));
414-
}
415-
applyBorderBox(area.getBBox(), true);
416-
}
417433
applyMargins(occupiedArea.getBBox(), true);
434+
435+
applyAbsolutePositionIfNeeded(layoutContext);
436+
418437
if (rotation != null) {
419438
applyRotationLayout(layoutContext.getArea().getBBox().clone());
420439
if (isNotFittingLayoutArea(layoutContext.getArea())) {
@@ -706,13 +725,15 @@ protected void endRotationIfApplied(PdfCanvas canvas) {
706725
}
707726
}
708727

728+
@Deprecated
729+
/**
730+
* @deprecated Will be made private and renamed in 7.1
731+
*/
709732
protected void correctPositionedLayout(Rectangle layoutBox) {
710733
if (isFixedLayout()) {
711734
float y = (float) this.getPropertyAsFloat(Property.Y);
712735
float relativeY = isFixedLayout() ? 0 : layoutBox.getY();
713736
move(0, relativeY + y - occupiedArea.getBBox().getY());
714-
} else {
715-
//TODO
716737
}
717738
}
718739

@@ -726,8 +747,6 @@ protected float applyBordersPaddingsMargins(Rectangle parentBBox, Border[] borde
726747
float x = (float) this.getPropertyAsFloat(Property.X);
727748
float relativeX = isFixedLayout() ? 0 : parentBBox.getX();
728749
parentBBox.setX(relativeX + x);
729-
} else if (isAbsolutePosition()) {
730-
applyAbsolutePosition(parentBBox);
731750
}
732751
}
733752
applyPaddings(parentBBox, paddings, false);

layout/src/main/java/com/itextpdf/layout/renderer/ImageRenderer.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,6 @@ public LayoutResult layout(LayoutContext layoutContext) {
121121
Border[] borders = getBorders();
122122
applyBorderBox(layoutBox, borders, false);
123123

124-
if (isAbsolutePosition()) {
125-
applyAbsolutePosition(layoutBox);
126-
}
127-
128124
occupiedArea = new LayoutArea(area.getPageNumber(), new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight(), 0, 0));
129125

130126
Float angle = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
@@ -257,6 +253,8 @@ public LayoutResult layout(LayoutContext layoutContext) {
257253
FloatingHelper.removeFloatsAboveRendererBottom(floatRendererAreas, this);
258254
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, floatRendererAreas, layoutContext.getArea().getBBox(), clearHeightCorrection, false);
259255

256+
applyAbsolutePositionIfNeeded(layoutContext);
257+
260258
return new MinMaxWidthLayoutResult(LayoutResult.FULL, editedArea, null, null, isPlacingForced ? this : null)
261259
.setMinMaxWidth(minMaxWidth);
262260
}

layout/src/main/java/com/itextpdf/layout/renderer/ParagraphRenderer.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ public LayoutResult layout(LayoutContext layoutContext) {
304304
applyBorderBox(occupiedArea.getBBox(), borders, true);
305305
applyMargins(occupiedArea.getBBox(), true);
306306

307+
applyAbsolutePositionIfNeeded(layoutContext);
308+
307309
LayoutArea editedArea = FloatingHelper.adjustResultOccupiedAreaForFloatAndClear(this, layoutContext.getFloatRendererAreas(), layoutContext.getArea().getBBox(), clearHeightCorrection, marginsCollapsingEnabled);
308310
if (wasHeightClipped) {
309311
return new MinMaxWidthLayoutResult(LayoutResult.FULL, editedArea, split[0], null).setMinMaxWidth(minMaxWidth);
@@ -386,13 +388,17 @@ public LayoutResult layout(LayoutContext layoutContext) {
386388
}
387389
applyVerticalAlignment();
388390
}
391+
389392
if (isPositioned) {
390393
correctPositionedLayout(layoutBox);
391394
}
392395

393396
applyPaddings(occupiedArea.getBBox(), paddings, true);
394397
applyBorderBox(occupiedArea.getBBox(), borders, true);
395398
applyMargins(occupiedArea.getBBox(), true);
399+
400+
applyAbsolutePositionIfNeeded(layoutContext);
401+
396402
if (rotation != null) {
397403
applyRotationLayout(layoutContext.getArea().getBBox().clone());
398404
if (isNotFittingLayoutArea(layoutContext.getArea())) {

0 commit comments

Comments
 (0)