Skip to content

Commit 3bb0c17

Browse files
committed
Merge branch 'feature/min_max_width' into develop
2 parents ec27580 + 9a4df5d commit 3bb0c17

File tree

13 files changed

+426
-75
lines changed

13 files changed

+426
-75
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,4 +399,15 @@ public T setMinHeight(float minHeight) {
399399
setProperty(Property.MIN_HEIGHT, minHeight);
400400
return (T) (Object) this;
401401
}
402+
403+
public T setMaxWidth(float maxWidth) {
404+
setProperty(Property.MAX_WIDTH, maxWidth);
405+
return (T) (Object) this;
406+
}
407+
408+
public T setMinWidth(float minWidth) {
409+
setProperty(Property.MIN_WIDTH, minWidth);
410+
return (T) (Object) this;
411+
}
412+
402413
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,16 @@ public Image setMinHeight(float minHeight) {
430430
return (Image) (Object) this;
431431
}
432432

433+
public Image setMaxWidth(float maxWidth) {
434+
setProperty(Property.MAX_WIDTH, maxWidth);
435+
return (Image) (Object) this;
436+
}
437+
438+
public Image setMinWidth(float minWidth) {
439+
setProperty(Property.MIN_WIDTH, minWidth);
440+
return (Image) (Object) this;
441+
}
442+
433443
/**
434444
* Gets scaled width of the image.
435445
* @return the current scaled width

layout/src/main/java/com/itextpdf/layout/margincollapse/MarginsCollapseHandler.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,8 @@ private static boolean rendererIsFloated(IRenderer renderer) {
558558

559559
private static float getModelTopMargin(IRenderer renderer) {
560560
Float margin = renderer.getModelElement().<Float>getProperty(Property.MARGIN_TOP);
561-
return margin != null ? (float) margin : 0;
561+
// TODO Concerning "renderer instanceof CellRenderer" check: may be try to apply more general solution in future
562+
return margin != null && !(renderer instanceof CellRenderer) ? (float) margin : 0;
562563
}
563564

564565
private static void ignoreModelTopMargin(IRenderer renderer) {
@@ -571,7 +572,8 @@ private static void overrideModelTopMargin(IRenderer renderer, float collapsedMa
571572

572573
private static float getModelBottomMargin(IRenderer renderer) {
573574
Float margin = renderer.getModelElement().<Float>getProperty(Property.MARGIN_BOTTOM);
574-
return margin != null ? (float) margin : 0;
575+
// TODO Concerning "renderer instanceof CellRenderer" check: may be try to apply more general solution in future
576+
return margin != null && !(renderer instanceof CellRenderer) ? (float) margin : 0;
575577
}
576578

577579
private static void ignoreModelBottomMargin(IRenderer renderer) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ private Property() {
137137
public static final int MARGIN_RIGHT = 45;
138138
public static final int MARGIN_TOP = 46;
139139
public static final int MAX_HEIGHT = 84;
140+
public static final int MAX_WIDTH = 107;
140141
public static final int MIN_HEIGHT = 85;
142+
public static final int MIN_WIDTH = 108;
141143

142144
public static final int OPACITY = 92;
143145
public static final int OVERFLOW = 102;

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

Lines changed: 99 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ public void drawBackground(DrawContext drawContext) {
492492
Rectangle backgroundArea = applyMargins(bBox, false);
493493
if (backgroundArea.getWidth() <= 0 || backgroundArea.getHeight() <= 0) {
494494
Logger logger = LoggerFactory.getLogger(AbstractRenderer.class);
495-
logger.error(MessageFormatUtil.format(LogMessageConstant.RECTANGLE_HAS_NEGATIVE_OR_ZERO_SIZES, "background"));
495+
logger.warn(MessageFormatUtil.format(LogMessageConstant.RECTANGLE_HAS_NEGATIVE_OR_ZERO_SIZES, "background"));
496496
return;
497497
}
498498
boolean backgroundAreaIsClipped = false;
@@ -517,7 +517,7 @@ public void drawBackground(DrawContext drawContext) {
517517
backgroundImage.getImage().getWidth(), backgroundImage.getImage().getHeight());
518518
if (imageRectangle.getWidth() <= 0 || imageRectangle.getHeight() <= 0) {
519519
Logger logger = LoggerFactory.getLogger(AbstractRenderer.class);
520-
logger.error(MessageFormatUtil.format(LogMessageConstant.RECTANGLE_HAS_NEGATIVE_OR_ZERO_SIZES, "background-image"));
520+
logger.warn(MessageFormatUtil.format(LogMessageConstant.RECTANGLE_HAS_NEGATIVE_OR_ZERO_SIZES, "background-image"));
521521
return;
522522
}
523523
applyBorderBox(backgroundArea, true);
@@ -991,26 +991,93 @@ static boolean isBorderBoxSizing(IRenderer renderer) {
991991

992992
/**
993993
* Retrieves element's fixed content box width, if it's set.
994-
* Takes into account {@link Property#BOX_SIZING} property value.
994+
* Takes into account {@link Property#BOX_SIZING}, {@link Property#MIN_WIDTH},
995+
* and {@link Property#MAX_WIDTH} properties.
995996
* @param parentBoxWidth width of the parent element content box.
996997
* If element has relative width, it will be
997998
* calculated relatively to this parameter.
998999
* @return element's fixed content box width or null if it's not set.
1000+
* @see AbstractRenderer#hasAbsoluteUnitValue(int)
9991001
*/
10001002
protected Float retrieveWidth(float parentBoxWidth) {
1003+
Float minWidth = retrieveUnitValue(parentBoxWidth, Property.MIN_WIDTH);
1004+
1005+
Float maxWidth = retrieveUnitValue(parentBoxWidth, Property.MAX_WIDTH);
1006+
if (maxWidth != null && minWidth != null && minWidth > maxWidth) {
1007+
maxWidth = minWidth;
1008+
}
1009+
10011010
Float width = retrieveUnitValue(parentBoxWidth, Property.WIDTH);
1011+
if (width != null) {
1012+
if (maxWidth != null) {
1013+
width = width > maxWidth ? maxWidth : width;
1014+
} else if (minWidth != null) {
1015+
width = width < minWidth ? minWidth : width;
1016+
}
1017+
} else if (maxWidth != null) {
1018+
width = maxWidth < parentBoxWidth ? maxWidth : null;
1019+
}
1020+
10021021
if (width != null && isBorderBoxSizing(this)) {
1003-
width = Math.max(0, (float)width - calculatePaddingBorderWidth(this));
1022+
width -= calculatePaddingBorderWidth(this);
1023+
}
1024+
1025+
return width != null ? Math.max(0, (float) width) : null;
1026+
}
1027+
1028+
/**
1029+
* Retrieves element's fixed content box max width, if it's set.
1030+
* Takes into account {@link Property#BOX_SIZING} and {@link Property#MIN_WIDTH} properties.
1031+
* @param parentBoxWidth width of the parent element content box.
1032+
* If element has relative width, it will be
1033+
* calculated relatively to this parameter.
1034+
* @return element's fixed content box max width or null if it's not set.
1035+
* @see AbstractRenderer#hasAbsoluteUnitValue(int)
1036+
*/
1037+
protected Float retrieveMaxWidth(float parentBoxWidth) {
1038+
Float maxWidth = retrieveUnitValue(parentBoxWidth, Property.MAX_WIDTH);
1039+
if (maxWidth != null) {
1040+
Float minWidth = retrieveUnitValue(parentBoxWidth, Property.MIN_WIDTH);
1041+
if (minWidth != null && minWidth > maxWidth) {
1042+
maxWidth = minWidth;
1043+
}
1044+
1045+
if (isBorderBoxSizing(this)) {
1046+
maxWidth -= calculatePaddingBorderWidth(this);
1047+
}
1048+
return maxWidth > 0 ? maxWidth : 0;
1049+
} else {
1050+
return null;
1051+
}
1052+
}
1053+
1054+
/**
1055+
* Retrieves element's fixed content box max width, if it's set.
1056+
* Takes into account {@link Property#BOX_SIZING} property value.
1057+
* @param parentBoxWidth width of the parent element content box.
1058+
* If element has relative width, it will be
1059+
* calculated relatively to this parameter.
1060+
* @return element's fixed content box max width or null if it's not set.
1061+
* @see AbstractRenderer#hasAbsoluteUnitValue(int)
1062+
*/
1063+
protected Float retrieveMinWidth(float parentBoxWidth) {
1064+
Float minWidth = retrieveUnitValue(parentBoxWidth, Property.MIN_WIDTH);
1065+
if (minWidth != null) {
1066+
if (isBorderBoxSizing(this)) {
1067+
minWidth -= calculatePaddingBorderWidth(this);
1068+
}
1069+
return minWidth > 0 ? minWidth : 0;
1070+
} else {
1071+
return null;
10041072
}
1005-
return width;
10061073
}
10071074

10081075
/**
10091076
* Updates fixed content box width value for this renderer.
10101077
* Takes into account {@link Property#BOX_SIZING} property value.
10111078
* @param updatedWidthValue element's new fixed content box width.
10121079
*/
1013-
protected void updateWidth(UnitValue updatedWidthValue) {
1080+
void updateWidth(UnitValue updatedWidthValue) {
10141081
if (updatedWidthValue.isPointValue() && isBorderBoxSizing(this)) {
10151082
updatedWidthValue.setValue(updatedWidthValue.getValue() + calculatePaddingBorderWidth(this));
10161083
}
@@ -1035,7 +1102,7 @@ protected Float retrieveHeight() {
10351102
* Takes into account {@link Property#BOX_SIZING} property value.
10361103
* @param updatedHeightValue element's new fixed content box height, shall be not null.
10371104
*/
1038-
protected void updateHeight(Float updatedHeightValue) {
1105+
void updateHeight(Float updatedHeightValue) {
10391106
if (isBorderBoxSizing(this)) {
10401107
updatedHeightValue += calculatePaddingBorderHeight(this);
10411108
}
@@ -1060,7 +1127,7 @@ protected Float retrieveMaxHeight() {
10601127
* Takes into account {@link Property#BOX_SIZING} property value.
10611128
* @param updatedMaxHeightValue element's new content box max-height, shall be not null.
10621129
*/
1063-
protected void updateMaxHeight(Float updatedMaxHeightValue) {
1130+
void updateMaxHeight(Float updatedMaxHeightValue) {
10641131
if (isBorderBoxSizing(this)) {
10651132
updatedMaxHeightValue += calculatePaddingBorderHeight(this);
10661133
}
@@ -1085,7 +1152,7 @@ protected Float retrieveMinHeight() {
10851152
* Takes into account {@link Property#BOX_SIZING} property value.
10861153
* @param updatedMinHeightValue element's new content box min-height, shall be not null.
10871154
*/
1088-
protected void updateMinHeight(Float updatedMinHeightValue) {
1155+
void updateMinHeight(Float updatedMinHeightValue) {
10891156
if (isBorderBoxSizing(this)) {
10901157
updatedMinHeightValue += calculatePaddingBorderHeight(this);
10911158
}
@@ -1095,12 +1162,11 @@ protected void updateMinHeight(Float updatedMinHeightValue) {
10951162
protected Float retrieveUnitValue(float basePercentValue, int property) {
10961163
UnitValue value = this.<UnitValue>getProperty(property);
10971164
if (value != null) {
1098-
if (value.getUnitType() == UnitValue.POINT) {
1099-
return value.getValue();
1100-
} else if (value.getUnitType() == UnitValue.PERCENT) {
1165+
if (value.getUnitType() == UnitValue.PERCENT) {
11011166
return value.getValue() * basePercentValue / 100;
11021167
} else {
1103-
throw new IllegalStateException("invalid unit type");
1168+
assert value.getUnitType() == UnitValue.POINT;
1169+
return value.getValue();
11041170
}
11051171
} else {
11061172
return null;
@@ -1346,12 +1412,15 @@ protected MinMaxWidth getMinMaxWidth(float availableWidth) {
13461412
}
13471413

13481414
protected boolean setMinMaxWidthBasedOnFixedWidth(MinMaxWidth minMaxWidth) {
1349-
UnitValue widthProp = this.<UnitValue>getProperty(Property.WIDTH);
1350-
Float width = retrieveWidth(0);
1351-
if (width != null && widthProp != null && !widthProp.isPercentValue()) {
1352-
minMaxWidth.setChildrenMaxWidth((float) width);
1353-
minMaxWidth.setChildrenMinWidth((float) width);
1354-
return true;
1415+
// retrieve returns max width, if there is no width.
1416+
if (hasAbsoluteUnitValue(Property.WIDTH)) {
1417+
//Renderer may override retrieveWidth, double check is required.
1418+
Float width = retrieveWidth(0);
1419+
if (width != null) {
1420+
minMaxWidth.setChildrenMaxWidth((float) width);
1421+
minMaxWidth.setChildrenMinWidth((float) width);
1422+
return true;
1423+
}
13551424
}
13561425
return false;
13571426
}
@@ -1618,6 +1687,17 @@ protected void overrideHeightProperties() {
16181687
}
16191688
}
16201689

1690+
/**
1691+
* Check if corresponding property has point value.
1692+
*
1693+
* @param property {@link Property}
1694+
* @return false if property value either null, or percent, otherwise true.
1695+
*/
1696+
protected boolean hasAbsoluteUnitValue(int property) {
1697+
UnitValue value = this.<UnitValue>getProperty(property);
1698+
return value != null && value.isPointValue();
1699+
}
1700+
16211701
boolean isFirstOnRootArea() {
16221702
boolean isFirstOnRootArea = true;
16231703
AbstractRenderer ancestor = this;

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

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ This file is part of the iText (R) project.
7171
import com.itextpdf.layout.property.Property;
7272
import com.itextpdf.layout.property.VerticalAlignment;
7373
import com.itextpdf.layout.property.ClearPropertyValue;
74-
import com.itextpdf.layout.property.UnitValue;
7574
import org.slf4j.Logger;
7675
import org.slf4j.LoggerFactory;
7776

@@ -126,9 +125,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
126125

127126
boolean isCellRenderer = this instanceof CellRenderer;
128127
if (marginsCollapsingEnabled) {
129-
if (!isCellRenderer) {
130-
marginsCollapseHandler.startMarginsCollapse(parentBBox);
131-
}
128+
marginsCollapseHandler.startMarginsCollapse(parentBBox);
132129
}
133130

134131
Border[] borders = getBorders();
@@ -137,9 +134,10 @@ public LayoutResult layout(LayoutContext layoutContext) {
137134
applyBordersPaddingsMargins(parentBBox, borders, paddings);
138135
OverflowPropertyValue overflowX = this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
139136
Float blockMaxHeight = retrieveMaxHeight();
140-
OverflowPropertyValue overflowY = (null == blockMaxHeight || blockMaxHeight > parentBBox.getHeight()) && !wasParentsHeightClipped ? OverflowPropertyValue.FIT : this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
137+
OverflowPropertyValue overflowY = (null == blockMaxHeight || blockMaxHeight > parentBBox.getHeight())
138+
&& !wasParentsHeightClipped ? OverflowPropertyValue.FIT : this.<OverflowPropertyValue>getProperty(Property.OVERFLOW_Y);
141139
applyWidth(parentBBox, blockWidth, overflowX);
142-
wasHeightClipped = applyHeight(parentBBox, blockMaxHeight, marginsCollapseHandler, isCellRenderer, wasParentsHeightClipped, overflowY);
140+
wasHeightClipped = applyMaxHeight(parentBBox, blockMaxHeight, marginsCollapseHandler, isCellRenderer, wasParentsHeightClipped, overflowY);
143141

144142
List<Rectangle> areas;
145143
if (isPositioned) {
@@ -210,7 +208,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
210208
break;
211209
}
212210

213-
if (marginsCollapsingEnabled && !isCellRenderer) {
211+
if (marginsCollapsingEnabled) {
214212
marginsCollapseHandler.endMarginsCollapse(layoutBox);
215213
}
216214

@@ -356,7 +354,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
356354
}
357355
}
358356
float overflowPartHeight = getOverflowPartHeight(overflowY, layoutBox);
359-
if (marginsCollapsingEnabled && !isCellRenderer) {
357+
if (marginsCollapsingEnabled) {
360358
marginsCollapseHandler.endMarginsCollapse(layoutBox);
361359
}
362360

@@ -763,15 +761,29 @@ protected void correctPositionedLayout(Rectangle layoutBox) {
763761
}
764762

765763
void applyWidth(Rectangle parentBBox, Float blockWidth, OverflowPropertyValue overflowX) {
764+
// maxWidth has already taken in attention in blockWidth,
765+
// therefore only `parentBBox > minWidth` needs to be checked.
766766
Float rotation = this.getPropertyAsFloat(Property.ROTATION_ANGLE);
767767

768-
if (blockWidth != null && (blockWidth < parentBBox.getWidth() || isPositioned() || rotation != null || (null != overflowX && !OverflowPropertyValue.FIT.equals(overflowX)))) {
768+
if (blockWidth != null && (
769+
blockWidth < parentBBox.getWidth() ||
770+
isPositioned() ||
771+
rotation != null ||
772+
(null != overflowX && !OverflowPropertyValue.FIT.equals(overflowX)))) {
769773
parentBBox.setWidth((float) blockWidth);
774+
} else {
775+
Float minWidth = retrieveMinWidth(parentBBox.getWidth());
776+
//Shall we check overflow-x here?
777+
if (minWidth != null && minWidth > parentBBox.getWidth()) {
778+
parentBBox.setWidth((float) minWidth);
779+
}
770780
}
771781
}
772782

773-
boolean applyHeight(Rectangle parentBBox, Float blockMaxHeight, MarginsCollapseHandler marginsCollapseHandler, boolean isCellRenderer, boolean wasParentsHeightClipped, OverflowPropertyValue overflowY) {
774-
if (null == blockMaxHeight || (blockMaxHeight >= parentBBox.getHeight() && (null == overflowY || OverflowPropertyValue.FIT.equals(overflowY)))) {
783+
boolean applyMaxHeight(Rectangle parentBBox, Float blockMaxHeight, MarginsCollapseHandler marginsCollapseHandler,
784+
boolean isCellRenderer, boolean wasParentsHeightClipped, OverflowPropertyValue overflowY) {
785+
if (null == blockMaxHeight || (blockMaxHeight >= parentBBox.getHeight()
786+
&& (null == overflowY || OverflowPropertyValue.FIT.equals(overflowY)))) {
775787
return false;
776788
}
777789
boolean wasHeightClipped = false;
@@ -812,17 +824,30 @@ protected float applyBordersPaddingsMargins(Rectangle parentBBox, Border[] borde
812824
protected MinMaxWidth getMinMaxWidth(float availableWidth) {
813825
MinMaxWidth minMaxWidth = new MinMaxWidth(calculateAdditionalWidth(this), availableWidth);
814826
if (!setMinMaxWidthBasedOnFixedWidth(minMaxWidth)) {
815-
AbstractWidthHandler handler = new MaxMaxWidthHandler(minMaxWidth);
816-
for (IRenderer childRenderer : childRenderers) {
817-
MinMaxWidth childMinMaxWidth;
818-
childRenderer.setParent(this);
819-
if (childRenderer instanceof AbstractRenderer) {
820-
childMinMaxWidth = ((AbstractRenderer) childRenderer).getMinMaxWidth(availableWidth);
821-
} else {
822-
childMinMaxWidth = MinMaxWidthUtils.countDefaultMinMaxWidth(childRenderer, availableWidth);
827+
Float minWidth = hasAbsoluteUnitValue(Property.MIN_WIDTH) ? retrieveMinWidth(0) : null;
828+
Float maxWidth = hasAbsoluteUnitValue(Property.MAX_WIDTH) ? retrieveMaxWidth(0) : null;
829+
if (minWidth == null || maxWidth == null) {
830+
AbstractWidthHandler handler = new MaxMaxWidthHandler(minMaxWidth);
831+
for (IRenderer childRenderer : childRenderers) {
832+
MinMaxWidth childMinMaxWidth;
833+
childRenderer.setParent(this);
834+
if (childRenderer instanceof AbstractRenderer) {
835+
childMinMaxWidth = ((AbstractRenderer) childRenderer).getMinMaxWidth(availableWidth);
836+
} else {
837+
childMinMaxWidth = MinMaxWidthUtils.countDefaultMinMaxWidth(childRenderer, availableWidth);
838+
}
839+
handler.updateMaxChildWidth(childMinMaxWidth.getMaxWidth());
840+
handler.updateMinChildWidth(childMinMaxWidth.getMinWidth());
823841
}
824-
handler.updateMaxChildWidth(childMinMaxWidth.getMaxWidth());
825-
handler.updateMinChildWidth(childMinMaxWidth.getMinWidth());
842+
}
843+
if (minWidth != null) {
844+
minMaxWidth.setChildrenMinWidth((float) minWidth);
845+
}
846+
if (maxWidth != null) {
847+
minMaxWidth.setChildrenMaxWidth((float) maxWidth);
848+
}
849+
if (minMaxWidth.getChildrenMinWidth() > minMaxWidth.getChildrenMaxWidth()) {
850+
minMaxWidth.setChildrenMaxWidth(minMaxWidth.getChildrenMaxWidth());
826851
}
827852
}
828853

0 commit comments

Comments
 (0)