Skip to content

Commit ca150c3

Browse files
committed
Fix border\margin\padding treating for grid and mutlicol
DEVSIX-8418
1 parent e49d12c commit ca150c3

File tree

14 files changed

+268
-215
lines changed

14 files changed

+268
-215
lines changed

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

Lines changed: 18 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.layout.renderer;
2424

2525
import com.itextpdf.kernel.geom.Rectangle;
26-
import com.itextpdf.layout.borders.Border;
2726
import com.itextpdf.layout.element.GridContainer;
2827
import com.itextpdf.layout.layout.LayoutArea;
2928
import com.itextpdf.layout.layout.LayoutContext;
@@ -45,6 +44,7 @@ This file is part of the iText (R) project.
4544
public class GridContainerRenderer extends BlockRenderer {
4645
private boolean isFirstLayout = true;
4746
private float containerHeight = 0.0f;
47+
private float containerWidth = 0.0f;
4848
/**
4949
* Creates a Grid renderer from its corresponding layout object.
5050
* @param modelElement the {@link GridContainer} which this object should manage
@@ -72,14 +72,13 @@ public LayoutResult layout(LayoutContext layoutContext) {
7272

7373
Rectangle actualBBox = layoutContext.getArea().getBBox().clone();
7474
Float blockWidth = retrieveWidth(actualBBox.getWidth());
75-
if (blockWidth != null) {
76-
actualBBox.setWidth((float) blockWidth);
77-
}
7875

7976
ContinuousContainer.setupContinuousContainerIfNeeded(this);
8077
applyPaddings(actualBBox, false);
8178
applyBorderBox(actualBBox, false);
8279
applyMargins(actualBBox, false);
80+
applyWidth(actualBBox, blockWidth, OverflowPropertyValue.VISIBLE);
81+
containerWidth = actualBBox.getWidth();
8382

8483
Float blockHeight = retrieveHeight();
8584
if (blockHeight != null && (float) blockHeight < actualBBox.getHeight()) {
@@ -100,7 +99,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
10099
} else {
101100
this.occupiedArea = calculateContainerOccupiedArea(layoutContext, false);
102101
return new LayoutResult(LayoutResult.PARTIAL, this.occupiedArea,
103-
createSplitRenderer(layoutResult.getSplitRenderers()),
102+
GridMulticolUtil.createSplitRenderer(layoutResult.getSplitRenderers(), this),
104103
createOverflowRenderer(layoutResult.getOverflowRenderers()));
105104
}
106105
}
@@ -141,18 +140,6 @@ private static Boolean determineCollapsingMargins(IRenderer renderer) {
141140
return Boolean.FALSE;
142141
}
143142

144-
private AbstractRenderer createSplitRenderer(List<IRenderer> children) {
145-
AbstractRenderer splitRenderer = (AbstractRenderer) getNextRenderer();
146-
splitRenderer.parent = parent;
147-
splitRenderer.modelElement = modelElement;
148-
splitRenderer.occupiedArea = occupiedArea;
149-
splitRenderer.isLastRendererForModelElement = false;
150-
splitRenderer.setChildRenderers(children);
151-
splitRenderer.addAllProperties(getOwnProperties());
152-
ContinuousContainer.setupContinuousContainerIfNeeded(splitRenderer);
153-
return splitRenderer;
154-
}
155-
156143
private AbstractRenderer createOverflowRenderer(List<IRenderer> children) {
157144
GridContainerRenderer overflowRenderer = (GridContainerRenderer) getNextRenderer();
158145
overflowRenderer.isFirstLayout = false;
@@ -273,67 +260,25 @@ private static LayoutContext getCellLayoutContext(LayoutContext layoutContext, R
273260
// Calculate grid container occupied area based on its width/height properties and cell layout areas
274261
private LayoutArea calculateContainerOccupiedArea(LayoutContext layoutContext, boolean isFull) {
275262
LayoutArea area = layoutContext.getArea().clone();
276-
final float totalHeight = updateOccupiedHeight(containerHeight, isFull);
277-
if (totalHeight < area.getBBox().getHeight() || isFull) {
278-
area.getBBox().setHeight(totalHeight);
279-
final Rectangle initialBBox = layoutContext.getArea().getBBox();
280-
area.getBBox().setY(initialBBox.getY() + initialBBox.getHeight() - area.getBBox().getHeight());
281-
recalculateHeightAndWidthAfterLayout(area.getBBox(), isFull);
282-
}
283-
return area;
284-
}
285-
286-
// Recalculate height/width after grid sizing and re-apply height/width properties
287-
private void recalculateHeightAndWidthAfterLayout(Rectangle bBox, boolean isFull) {
288-
Float height = retrieveHeight();
289-
if (height != null) {
290-
height = updateOccupiedHeight((float) height, isFull);
291-
float heightDelta = bBox.getHeight() - (float) height;
292-
bBox.moveUp(heightDelta);
293-
bBox.setHeight((float) height);
294-
}
295-
Float blockWidth = retrieveWidth(bBox.getWidth());
296-
if (blockWidth != null) {
297-
bBox.setWidth((float) blockWidth);
298-
}
299-
}
263+
final Rectangle areaBBox = area.getBBox();
300264

301-
private float updateOccupiedHeight(float initialHeight, boolean isFull) {
302-
if (isFull) {
303-
initialHeight += safelyRetrieveFloatProperty(Property.PADDING_BOTTOM);
304-
initialHeight += safelyRetrieveFloatProperty(Property.MARGIN_BOTTOM);
305-
if (!this.hasOwnProperty(Property.BORDER) || this.<Border>getProperty(Property.BORDER) == null) {
306-
initialHeight += safelyRetrieveFloatProperty(Property.BORDER_BOTTOM);
265+
final float totalContainerHeight = GridMulticolUtil.updateOccupiedHeight(containerHeight, isFull, isFirstLayout, this);
266+
if (totalContainerHeight < areaBBox.getHeight() || isFull) {
267+
Float height = retrieveHeight();
268+
if (height == null) {
269+
areaBBox.setHeight(totalContainerHeight);
270+
} else {
271+
height = GridMulticolUtil.updateOccupiedHeight((float) height, isFull, isFirstLayout, this);
272+
areaBBox.setHeight((float) height);
307273
}
308274
}
309-
initialHeight += safelyRetrieveFloatProperty(Property.PADDING_TOP);
310-
311-
initialHeight += safelyRetrieveFloatProperty(Property.MARGIN_TOP);
312275

313-
if (!this.hasOwnProperty(Property.BORDER) || this.<Border>getProperty(Property.BORDER) == null) {
314-
initialHeight += safelyRetrieveFloatProperty(Property.BORDER_TOP);
315-
}
276+
final Rectangle initialBBox = layoutContext.getArea().getBBox();
277+
areaBBox.setY(initialBBox.getY() + initialBBox.getHeight() - areaBBox.getHeight());
316278

317-
// isFirstLayout is necessary to handle the case when grid container laid out on more
318-
// than 2 pages, and on the last page layout result is full, but there is no bottom border
319-
float TOP_AND_BOTTOM = isFull && isFirstLayout ? 2 : 1;
320-
//If container laid out on more than 3 pages, then it is a page where there are no bottom and top borders
321-
if (!isFull && !isFirstLayout) {
322-
TOP_AND_BOTTOM = 0;
323-
}
324-
initialHeight += safelyRetrieveFloatProperty(Property.BORDER) * TOP_AND_BOTTOM;
325-
return initialHeight;
326-
}
327-
328-
private float safelyRetrieveFloatProperty(int property) {
329-
final Object value = this.<Object>getProperty(property);
330-
if (value instanceof UnitValue) {
331-
return ((UnitValue) value).getValue();
332-
}
333-
if (value instanceof Border) {
334-
return ((Border) value).getWidth();
335-
}
336-
return 0F;
279+
final float totalContainerWidth = GridMulticolUtil.updateOccupiedWidth(containerWidth, this);
280+
areaBBox.setWidth(totalContainerWidth);
281+
return area;
337282
}
338283

339284
// Grid layout algorithm is based on a https://drafts.csswg.org/css-grid/#layout-algorithm
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2024 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.layout.renderer;
24+
25+
import com.itextpdf.layout.borders.Border;
26+
import com.itextpdf.layout.properties.ContinuousContainer;
27+
import com.itextpdf.layout.properties.Property;
28+
import com.itextpdf.layout.properties.UnitValue;
29+
30+
import java.util.List;
31+
32+
/**
33+
* The class stores common logic for multicol and grid layout.
34+
*/
35+
final class GridMulticolUtil {
36+
private GridMulticolUtil() {
37+
// do nothing
38+
}
39+
40+
/**
41+
* Creates a split renderer.
42+
*
43+
* @param children children of the split renderer
44+
* @param renderer parent renderer
45+
*
46+
* @return a new {@link AbstractRenderer} instance
47+
*/
48+
static AbstractRenderer createSplitRenderer(List<IRenderer> children, AbstractRenderer renderer) {
49+
AbstractRenderer splitRenderer = (AbstractRenderer) renderer.getNextRenderer();
50+
splitRenderer.parent = renderer.parent;
51+
splitRenderer.modelElement = renderer.modelElement;
52+
splitRenderer.occupiedArea = renderer.occupiedArea;
53+
splitRenderer.isLastRendererForModelElement = false;
54+
splitRenderer.setChildRenderers(children);
55+
splitRenderer.addAllProperties(renderer.getOwnProperties());
56+
ContinuousContainer.setupContinuousContainerIfNeeded(splitRenderer);
57+
return splitRenderer;
58+
}
59+
60+
static float updateOccupiedWidth(float initialWidth, AbstractRenderer renderer) {
61+
float result = initialWidth;
62+
result += safelyRetrieveFloatProperty(Property.PADDING_LEFT, renderer);
63+
result += safelyRetrieveFloatProperty(Property.PADDING_RIGHT, renderer);
64+
65+
result += safelyRetrieveFloatProperty(Property.MARGIN_LEFT, renderer);
66+
result += safelyRetrieveFloatProperty(Property.MARGIN_RIGHT, renderer);
67+
68+
if (!renderer.hasOwnProperty(Property.BORDER) || renderer.<Border>getProperty(Property.BORDER) == null) {
69+
result += safelyRetrieveFloatProperty(Property.BORDER_LEFT, renderer);
70+
}
71+
if (!renderer.hasOwnProperty(Property.BORDER) || renderer.<Border>getProperty(Property.BORDER) == null) {
72+
result += safelyRetrieveFloatProperty(Property.BORDER_RIGHT, renderer);
73+
}
74+
result += safelyRetrieveFloatProperty(Property.BORDER, renderer) * 2;
75+
76+
return result;
77+
}
78+
79+
static float updateOccupiedHeight(float initialHeight, boolean isFull, boolean isFirstLayout,
80+
AbstractRenderer renderer) {
81+
82+
float result = initialHeight;
83+
if (isFull) {
84+
result += safelyRetrieveFloatProperty(Property.PADDING_BOTTOM, renderer);
85+
result += safelyRetrieveFloatProperty(Property.MARGIN_BOTTOM, renderer);
86+
if (!renderer.hasOwnProperty(Property.BORDER) || renderer.<Border>getProperty(Property.BORDER) == null) {
87+
result += safelyRetrieveFloatProperty(Property.BORDER_BOTTOM, renderer);
88+
}
89+
}
90+
result += safelyRetrieveFloatProperty(Property.PADDING_TOP, renderer);
91+
92+
result += safelyRetrieveFloatProperty(Property.MARGIN_TOP, renderer);
93+
94+
if (!renderer.hasOwnProperty(Property.BORDER) || renderer.<Border>getProperty(Property.BORDER) == null) {
95+
result += safelyRetrieveFloatProperty(Property.BORDER_TOP, renderer);
96+
}
97+
98+
// isFirstLayout is necessary to handle the case when multicol container layouted in more
99+
// than 2 pages, and on the last page layout result is full, but there is no bottom border
100+
float TOP_AND_BOTTOM = isFull && isFirstLayout ? 2 : 1;
101+
// Multicol container layouted in more than 3 pages, and there is a page where there are no bottom and top borders
102+
if (!isFull && !isFirstLayout) {
103+
TOP_AND_BOTTOM = 0;
104+
}
105+
result += safelyRetrieveFloatProperty(Property.BORDER, renderer) * TOP_AND_BOTTOM;
106+
return result;
107+
}
108+
109+
private static float safelyRetrieveFloatProperty(int property, AbstractRenderer renderer) {
110+
final Object value = renderer.<Object>getProperty(property);
111+
if (value instanceof UnitValue) {
112+
return ((UnitValue) value).getValue();
113+
}
114+
if (value instanceof Border) {
115+
return ((Border) value).getWidth();
116+
}
117+
return 0F;
118+
}
119+
}

0 commit comments

Comments
 (0)