Skip to content

Commit fc607f8

Browse files
committed
Initial overflow support
If block container has overflow property, iText will use its width and height despite it might be bigger than parent's. If direct parent container has overflow property, iText will process Text and Image elements as if there is enough space for them. Clip and path area in draw() to perform HIDDEN overflow DEVSIX-992
1 parent 79e118a commit fc607f8

File tree

14 files changed

+238
-28
lines changed

14 files changed

+238
-28
lines changed

layout/src/main/java/com/itextpdf/layout/element/BlockElement.java

Lines changed: 54 additions & 0 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.element;
4545

4646
import com.itextpdf.kernel.pdf.tagutils.IAccessibleElement;
47+
import com.itextpdf.layout.property.OverflowPropertyValue;
4748
import com.itextpdf.layout.property.Property;
4849
import com.itextpdf.layout.property.VerticalAlignment;
4950

@@ -63,6 +64,18 @@ public abstract class BlockElement<T extends IElement> extends AbstractElement<T
6364
protected BlockElement() {
6465
}
6566

67+
@Override
68+
public <T1> T1 getDefaultProperty(int property) {
69+
switch (property) {
70+
case Property.OVERFLOW:
71+
case Property.OVERFLOW_X:
72+
case Property.OVERFLOW_Y:
73+
return (T1) (Object) OverflowPropertyValue.FIT;
74+
default:
75+
return super.<T1>getDefaultProperty(property);
76+
}
77+
}
78+
6679
/**
6780
* Gets the current left margin width of the element.
6881
*
@@ -386,4 +399,45 @@ public T setMinHeight(float minHeight) {
386399
setProperty(Property.MIN_HEIGHT, minHeight);
387400
return (T) (Object) this;
388401
}
402+
403+
/**
404+
* Sets the overflow-x value.
405+
*
406+
* @param overflow-x the overflow value
407+
* @return this element
408+
*/
409+
public T setOverflowX(OverflowPropertyValue overflow) {
410+
setProperty(Property.OVERFLOW_X, overflow);
411+
return (T) (Object) this;
412+
}
413+
414+
/**
415+
* Sets the overflow-y value.
416+
*
417+
* @param overflow-y the overflow value
418+
* @return this element
419+
*/
420+
public T setOverflowY(OverflowPropertyValue overflow) {
421+
setProperty(Property.OVERFLOW_Y, overflow);
422+
return (T) (Object) this;
423+
}
424+
425+
/**
426+
* Gets the overflow-x value of the element.
427+
*
428+
* @return the overflow-x value of the element.
429+
*/
430+
public OverflowPropertyValue getOverflowX() {
431+
return this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
432+
}
433+
434+
/**
435+
* Gets the overflow-y value of the element.
436+
*
437+
* @return the overflow-y value of the element.
438+
*/
439+
public OverflowPropertyValue getOverflowY() {
440+
return this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
441+
}
442+
389443
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.itextpdf.layout.property;
2+
3+
/**
4+
* The possible values for the type of overflow of {@link
5+
* com.itextpdf.layout.element.BlockElement}.
6+
*/
7+
public enum OverflowPropertyValue {
8+
FIT,
9+
VISIBLE,
10+
HIDDEN
11+
}

layout/src/main/java/com/itextpdf/layout/property/Property.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ private Property() {
139139
public static final int MIN_HEIGHT = 85;
140140

141141
public static final int OPACITY = 92;
142+
public static final int OVERFLOW = 102;
143+
public static final int OVERFLOW_X = 103;
144+
public static final int OVERFLOW_Y = 104;
142145
public static final int PADDING_BOTTOM = 47;
143146
public static final int PADDING_LEFT = 48;
144147
public static final int PADDING_RIGHT = 49;

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

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ This file is part of the iText (R) project.
6868
import com.itextpdf.layout.minmaxwidth.MinMaxWidthUtils;
6969
import com.itextpdf.layout.property.AreaBreakType;
7070
import com.itextpdf.layout.property.FloatPropertyValue;
71+
import com.itextpdf.layout.property.OverflowPropertyValue;
7172
import com.itextpdf.layout.property.Property;
7273
import com.itextpdf.layout.property.UnitValue;
7374
import com.itextpdf.layout.property.VerticalAlignment;
@@ -130,26 +131,33 @@ public LayoutResult layout(LayoutContext layoutContext) {
130131
float[] paddings = getPaddings();
131132
applyBordersPaddingsMargins(parentBBox, borders, paddings);
132133

133-
if (blockWidth != null && (blockWidth < parentBBox.getWidth() || isPositioned || rotation != null)) {
134+
OverflowPropertyValue overflowX = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
135+
OverflowPropertyValue overflowY = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
136+
137+
if (blockWidth != null && (blockWidth < parentBBox.getWidth() || isPositioned || rotation != null || (null != overflowX && !OverflowPropertyValue.FIT.equals(overflowX)))) {
134138
// TODO DEVSIX-1174
135139
UnitValue widthVal = this.<UnitValue>getProperty(Property.WIDTH);
136-
if (widthVal != null && widthVal.isPercentValue() && widthVal.getValue() == 100) {
140+
if (widthVal != null && widthVal.isPercentValue() && widthVal.getValue() == 100 && (null == overflowX || OverflowPropertyValue.FIT.equals(overflowX))) {
137141
} else {
138142
parentBBox.setWidth((float) blockWidth);
139143
}
140144
}
141145

142146
Float blockMaxHeight = retrieveMaxHeight();
143-
if (!isFixedLayout() && null != blockMaxHeight && blockMaxHeight < parentBBox.getHeight()
147+
if (!isFixedLayout() && null != blockMaxHeight && (blockMaxHeight < parentBBox.getHeight() || (null != overflowY && !OverflowPropertyValue.FIT.equals(overflowY)))
144148
&& !Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT))) {
149+
if (blockMaxHeight < parentBBox.getHeight()) {
150+
wasHeightClipped = true;
151+
}
145152
float heightDelta = parentBBox.getHeight() - (float) blockMaxHeight;
146153
if (marginsCollapsingEnabled && !isCellRenderer) {
147154
marginsCollapseHandler.processFixedHeightAdjustment(heightDelta);
148155
}
149156
parentBBox.moveUp(heightDelta).setHeight((float) blockMaxHeight);
150-
wasHeightClipped = true;
151157
}
152158

159+
Rectangle contentBoxToFit = parentBBox.clone();
160+
153161
List<Rectangle> areas;
154162
if (isPositioned) {
155163
areas = Collections.singletonList(parentBBox);
@@ -188,7 +196,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
188196
|| Boolean.TRUE.equals(getPropertyAsBoolean(Property.FILL_AVAILABLE_AREA))) {
189197
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), layoutBox));
190198
} else if (result.getOccupiedArea() != null && result.getStatus() != LayoutResult.NOTHING) {
191-
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), result.getOccupiedArea().getBBox()));
199+
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), result.getOccupiedArea().getBBox()).setWidth(occupiedArea.getBBox().getWidth()));
192200
}
193201

194202
if (FloatingHelper.isRendererFloating(this) || isCellRenderer) {
@@ -349,7 +357,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
349357

350358
if (result.getOccupiedArea() != null) {
351359
if (!FloatingHelper.isRendererFloating(childRenderer)) { // this check is needed only if margins collapsing is enabled
352-
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), result.getOccupiedArea().getBBox()));
360+
occupiedArea.setBBox(Rectangle.getCommonRectangle(occupiedArea.getBBox(), result.getOccupiedArea().getBBox()).setWidth(occupiedArea.getBBox().getWidth()));
353361
}
354362
}
355363
if (marginsCollapsingEnabled) {
@@ -388,7 +396,10 @@ public LayoutResult layout(LayoutContext layoutContext) {
388396
if (isFixedLayout()) {
389397
occupiedArea.getBBox().moveDown((float) blockMinHeight - occupiedArea.getBBox().getHeight()).setHeight((float) blockMinHeight);
390398
} else {
391-
float blockBottom = Math.max(occupiedArea.getBBox().getBottom() - ((float) blockMinHeight - occupiedArea.getBBox().getHeight()), layoutBox.getBottom());
399+
float blockBottom = occupiedArea.getBBox().getBottom() - ((float) blockMinHeight - occupiedArea.getBBox().getHeight());
400+
if ((null == overflowY || OverflowPropertyValue.FIT.equals(overflowY)) && blockBottom < layoutBox.getBottom()) {
401+
blockBottom = layoutBox.getBottom();
402+
}
392403
occupiedArea.getBBox()
393404
.increaseHeight(occupiedArea.getBBox().getBottom() - blockBottom)
394405
.setY(blockBottom);
@@ -426,6 +437,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
426437
correctPositionedLayout(layoutBox);
427438
}
428439

440+
float overflowPartHeight = getOverflowPartHeight(overflowY, contentBoxToFit);
429441
applyPaddings(occupiedArea.getBBox(), paddings, true);
430442
applyBorderBox(occupiedArea.getBBox(), borders, true);
431443
applyMargins(occupiedArea.getBBox(), true);
@@ -459,6 +471,9 @@ public LayoutResult layout(LayoutContext layoutContext) {
459471
}
460472
}
461473
}
474+
if (wasHeightClipped) {
475+
occupiedArea.getBBox().moveUp(overflowPartHeight).decreaseHeight(overflowPartHeight);
476+
}
462477

463478
if (null == overflowRenderer) {
464479
return new LayoutResult(LayoutResult.FULL, editedArea, null, null, causeOfNothing);
@@ -535,8 +550,29 @@ public void draw(DrawContext drawContext) {
535550

536551
drawBackground(drawContext);
537552
drawBorder(drawContext);
553+
554+
OverflowPropertyValue overflowX = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
555+
OverflowPropertyValue overflowY = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
556+
boolean processOverflow = OverflowPropertyValue.HIDDEN.equals(overflowX) || OverflowPropertyValue.HIDDEN.equals(overflowY);
557+
558+
if (processOverflow) {
559+
drawContext.getCanvas().saveState();
560+
Rectangle clippedArea = drawContext.getDocument().getPage(occupiedArea.getPageNumber()).getPageSize();
561+
Rectangle area = getBorderAreaBBox();
562+
if (OverflowPropertyValue.HIDDEN.equals(overflowX)) {
563+
clippedArea.setX(area.getX()).setWidth(area.getWidth());
564+
}
565+
if (OverflowPropertyValue.HIDDEN.equals(overflowY)) {
566+
clippedArea.setY(area.getY()).setHeight(area.getHeight());
567+
}
568+
drawContext.getCanvas().rectangle(clippedArea).clip().newPath();
569+
}
570+
538571
drawChildren(drawContext);
539572
drawPositionedChildren(drawContext);
573+
if (processOverflow) {
574+
drawContext.getCanvas().restoreState();
575+
}
540576

541577
endRotationIfApplied(drawContext.getCanvas());
542578
endElementOpacityApplying(drawContext);
@@ -735,6 +771,16 @@ protected void correctPositionedLayout(Rectangle layoutBox) {
735771
}
736772
}
737773

774+
protected float getOverflowPartHeight(OverflowPropertyValue overflowY, Rectangle parentBox) {
775+
float difference = 0;
776+
if (null != overflowY && OverflowPropertyValue.FIT != overflowY) {
777+
if (occupiedArea.getBBox().getBottom() < parentBox.getBottom()) {
778+
difference = parentBox.getBottom() - occupiedArea.getBBox().getBottom();
779+
}
780+
}
781+
return difference;
782+
}
783+
738784
protected float applyBordersPaddingsMargins(Rectangle parentBBox, Border[] borders, float[] paddings) {
739785
float parentWidth = parentBBox.getWidth();
740786

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,16 +224,14 @@ static void removeFloatsAboveRendererBottom(List<Rectangle> floatRendererAreas,
224224
static LayoutArea adjustResultOccupiedAreaForFloatAndClear(IRenderer renderer, List<Rectangle> floatRendererAreas,
225225
Rectangle parentBBox, float clearHeightCorrection, boolean marginsCollapsingEnabled) {
226226
LayoutArea occupiedArea = renderer.getOccupiedArea();
227-
LayoutArea editedArea = occupiedArea;
227+
LayoutArea editedArea = occupiedArea.clone();
228228
if (isRendererFloating(renderer)) {
229-
editedArea = occupiedArea.clone();
230229
if (occupiedArea.getBBox().getWidth() > 0) {
231230
floatRendererAreas.add(occupiedArea.getBBox());
232231
}
233232
editedArea.getBBox().setY(parentBBox.getTop());
234233
editedArea.getBBox().setHeight(0);
235234
} else if (clearHeightCorrection > 0 && !marginsCollapsingEnabled) {
236-
editedArea = occupiedArea.clone();
237235
editedArea.getBBox().increaseHeight(clearHeightCorrection);
238236
}
239237

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ This file is part of the iText (R) project.
6565
import com.itextpdf.layout.layout.MinMaxWidthLayoutResult;
6666
import com.itextpdf.layout.minmaxwidth.MinMaxWidth;
6767
import com.itextpdf.layout.property.FloatPropertyValue;
68+
import com.itextpdf.layout.property.OverflowPropertyValue;
6869
import com.itextpdf.layout.property.Property;
6970
import com.itextpdf.layout.property.UnitValue;
7071
import org.slf4j.Logger;
@@ -121,6 +122,12 @@ public LayoutResult layout(LayoutContext layoutContext) {
121122
Border[] borders = getBorders();
122123
applyBorderBox(layoutBox, borders, false);
123124

125+
OverflowPropertyValue overflowX = this.parent.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
126+
OverflowPropertyValue overflowY = this.parent.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
127+
boolean processOverflow = (null != overflowX && !OverflowPropertyValue.FIT.equals(overflowX)) || (null != overflowY && !OverflowPropertyValue.FIT.equals(overflowY));
128+
if (isAbsolutePosition()) {
129+
applyAbsolutePosition(layoutBox);
130+
}
124131
occupiedArea = new LayoutArea(area.getPageNumber(), new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight(), 0, 0));
125132

126133
Float angle = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
@@ -206,7 +213,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
206213
// indicates whether the placement is forced
207214
boolean isPlacingForced = false;
208215
if (width > layoutBox.getWidth() || height > layoutBox.getHeight()) {
209-
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT))) {
216+
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)) || processOverflow) {
210217
isPlacingForced = true;
211218
} else {
212219
return new MinMaxWidthLayoutResult(LayoutResult.NOTHING, occupiedArea, null, this, this);
@@ -328,7 +335,26 @@ public void draw(DrawContext drawContext) {
328335

329336
PdfXObject xObject = ((Image) (getModelElement())).getXObject();
330337
beginElementOpacityApplying(drawContext);
338+
OverflowPropertyValue overflowX = this.parent.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
339+
OverflowPropertyValue overflowY = this.parent.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
340+
boolean processOverflow = OverflowPropertyValue.HIDDEN.equals(overflowX) || OverflowPropertyValue.HIDDEN.equals(overflowY);
341+
if (processOverflow) {
342+
drawContext.getCanvas().saveState();
343+
344+
Rectangle clippedArea = drawContext.getDocument().getPage(occupiedArea.getPageNumber()).getPageSize();
345+
if (OverflowPropertyValue.HIDDEN.equals(overflowX)) {
346+
clippedArea.setX(occupiedArea.getBBox().getX()).setWidth(occupiedArea.getBBox().getWidth());
347+
}
348+
if (OverflowPropertyValue.HIDDEN.equals(overflowY)) {
349+
clippedArea.setY(occupiedArea.getBBox().getY()).setHeight(occupiedArea.getBBox().getHeight());
350+
}
351+
352+
drawContext.getCanvas().rectangle(clippedArea).clip().newPath();
353+
}
331354
canvas.addXObject(xObject, matrix[0], matrix[1], matrix[2], matrix[3], (float) fixedXPosition + deltaX, (float) fixedYPosition);
355+
if (processOverflow) {
356+
drawContext.getCanvas().restoreState();
357+
}
332358
endElementOpacityApplying(drawContext);
333359
if (Boolean.TRUE.equals(getPropertyAsBoolean(Property.FLUSH_ON_DRAW))) {
334360
xObject.flush();

0 commit comments

Comments
 (0)