Skip to content

Commit 86b649f

Browse files
author
Dmitry Radchuk
committed
Refactor balancing algorithm and continious container support in ColumnContainerRenderer
DEVSIX-7587
1 parent cc280ce commit 86b649f

File tree

29 files changed

+494
-477
lines changed

29 files changed

+494
-477
lines changed

layout/src/main/java/com/itextpdf/layout/element/ColumnContainer.java renamed to layout/src/main/java/com/itextpdf/layout/element/MulticolContainer.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,25 @@ This file is part of the iText (R) project.
2222
*/
2323
package com.itextpdf.layout.element;
2424

25-
import com.itextpdf.layout.renderer.ColumnContainerRenderer;
25+
import com.itextpdf.layout.renderer.MulticolRenderer;
2626
import com.itextpdf.layout.renderer.IRenderer;
2727

2828
import java.util.Map;
2929

3030
/**
3131
* represents a container of the column objects.
3232
*/
33-
public class ColumnContainer extends Div {
33+
public class MulticolContainer extends Div {
3434

3535
/**
36-
* Creates new {@link ColumnContainer} instance.
36+
* Creates new {@link MulticolContainer} instance.
3737
*/
38-
public ColumnContainer() {
38+
public MulticolContainer() {
3939
super();
4040
}
4141

4242
/**
43-
* Copies all properties of {@link ColumnContainer} to its child elements.
43+
* Copies all properties of {@link MulticolContainer} to its child elements.
4444
*/
4545
public void copyAllPropertiesToChildren() {
4646
for (final IElement child : this.getChildren()) {
@@ -52,7 +52,7 @@ public void copyAllPropertiesToChildren() {
5252

5353
@Override
5454
protected IRenderer makeNewRenderer() {
55-
return new ColumnContainerRenderer(this);
55+
return new MulticolRenderer(this);
5656
}
5757

5858
}

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

Lines changed: 0 additions & 196 deletions
This file was deleted.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2023 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.kernel.geom.Rectangle;
26+
import com.itextpdf.layout.element.MulticolContainer;
27+
import com.itextpdf.layout.layout.LayoutArea;
28+
import com.itextpdf.layout.layout.LayoutContext;
29+
import com.itextpdf.layout.layout.LayoutResult;
30+
import com.itextpdf.layout.properties.Property;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
34+
35+
/**
36+
* Represents a renderer for columns.
37+
*/
38+
public class MulticolRenderer extends AbstractRenderer {
39+
40+
private static final int MAX_RELAYOUT_COUNT = 4;
41+
private static final float ZERO_DELTA = 0.0001f;
42+
43+
private BlockRenderer elementRenderer;
44+
private int columnCount;
45+
private float columnWidth;
46+
private float approximateHeight;
47+
48+
/**
49+
* Creates a DivRenderer from its corresponding layout object.
50+
*
51+
* @param modelElement the {@link MulticolContainer} which this object should manage
52+
*/
53+
public MulticolRenderer(MulticolContainer modelElement) {
54+
super(modelElement);
55+
}
56+
57+
@Override
58+
public IRenderer getNextRenderer() {
59+
logWarningIfGetNextRendererNotOverridden(MulticolRenderer.class, this.getClass());
60+
return new MulticolRenderer((MulticolContainer) modelElement);
61+
}
62+
63+
/**
64+
* {@inheritDoc}
65+
*/
66+
@Override
67+
public LayoutResult layout(LayoutContext layoutContext) {
68+
((MulticolContainer) this.getModelElement()).copyAllPropertiesToChildren();
69+
this.setProperty(Property.TREAT_AS_CONTINUOUS_CONTAINER, Boolean.TRUE);
70+
final Rectangle initialBBox = layoutContext.getArea().getBBox();
71+
columnCount = (int) this.<Integer>getProperty(Property.COLUMN_COUNT);
72+
columnWidth = initialBBox.getWidth() / columnCount;
73+
if (this.elementRenderer == null) {
74+
// initialize elementRenderer on first layout when first child represents renderer of element which
75+
// should be layouted in multicol, because on the next layouts this can have multiple children
76+
elementRenderer = getElementsRenderer();
77+
}
78+
//It is necessary to set parent, because during relayout elementRenderer's parent gets cleaned up
79+
elementRenderer.setParent(this);
80+
LayoutResult prelayoutResult = elementRenderer.layout(
81+
new LayoutContext(new LayoutArea(1, new Rectangle(columnWidth, INF))));
82+
if (prelayoutResult.getStatus() != LayoutResult.FULL) {
83+
return new LayoutResult(LayoutResult.NOTHING, null, null, this, elementRenderer);
84+
}
85+
86+
approximateHeight = prelayoutResult.getOccupiedArea().getBBox().getHeight() / columnCount;
87+
88+
List<IRenderer> container = balanceContentAndLayoutColumns(layoutContext);
89+
90+
this.occupiedArea = calculateContainerOccupiedArea(layoutContext);
91+
this.setChildRenderers(container);
92+
LayoutResult result = new LayoutResult(LayoutResult.FULL, this.occupiedArea, this, null);
93+
94+
// process some properties (keepTogether, margin collapsing), area breaks, adding height
95+
// Check what we do at the end of BlockRenderer
96+
97+
return result;
98+
}
99+
100+
private List<IRenderer> balanceContentAndLayoutColumns(LayoutContext prelayoutContext) {
101+
Float additionalHeightPerIteration = null;
102+
List<IRenderer> container = new ArrayList<>();
103+
int counter = MAX_RELAYOUT_COUNT;
104+
while (counter-- > 0) {
105+
IRenderer overflowRenderer = layoutColumnsAndReturnOverflowRenderer(prelayoutContext, container);
106+
if (overflowRenderer == null) {
107+
return container;
108+
}
109+
if (additionalHeightPerIteration == null) {
110+
LayoutResult overflowResult = overflowRenderer.layout(
111+
new LayoutContext(new LayoutArea(1, new Rectangle(columnWidth, INF))));
112+
additionalHeightPerIteration = overflowResult.getOccupiedArea().getBBox().getHeight() / MAX_RELAYOUT_COUNT;
113+
}
114+
if (Math.abs(additionalHeightPerIteration.floatValue()) <= ZERO_DELTA) {
115+
return container;
116+
}
117+
approximateHeight += additionalHeightPerIteration.floatValue();
118+
}
119+
return container;
120+
}
121+
122+
private LayoutArea calculateContainerOccupiedArea(LayoutContext layoutContext) {
123+
LayoutArea area = layoutContext.getArea().clone();
124+
area.getBBox().setHeight(approximateHeight);
125+
Rectangle initialBBox = layoutContext.getArea().getBBox();
126+
area.getBBox().setY(initialBBox.getY() + initialBBox.getHeight() - area.getBBox().getHeight());
127+
return area;
128+
}
129+
130+
private BlockRenderer getElementsRenderer() {
131+
if (!(getChildRenderers().size() == 1 && getChildRenderers().get(0) instanceof BlockRenderer)) {
132+
throw new IllegalStateException("Invalid child renderers, it should be one and be a block element");
133+
}
134+
return (BlockRenderer) getChildRenderers().get(0);
135+
}
136+
137+
private IRenderer layoutColumnsAndReturnOverflowRenderer(LayoutContext preLayoutContext, List<IRenderer> container) {
138+
container.clear();
139+
Rectangle initialBBox = preLayoutContext.getArea().getBBox();
140+
IRenderer renderer = elementRenderer;
141+
for (int i = 0; i < columnCount && renderer != null; i++) {
142+
LayoutArea tempArea = preLayoutContext.getArea().clone();
143+
tempArea.getBBox().setWidth(columnWidth);
144+
tempArea.getBBox().setHeight(approximateHeight);
145+
tempArea.getBBox().setX(initialBBox.getX() + columnWidth * i);
146+
tempArea.getBBox().setY(initialBBox.getY() + initialBBox.getHeight() - tempArea.getBBox().getHeight());
147+
LayoutContext columnContext = new LayoutContext(tempArea, preLayoutContext.getMarginsCollapseInfo(),
148+
preLayoutContext.getFloatRendererAreas(), preLayoutContext.isClippedHeight());
149+
150+
LayoutResult tempResultColumn = renderer.layout(columnContext);
151+
if (tempResultColumn.getSplitRenderer() == null) {
152+
container.add(renderer);
153+
} else {
154+
container.add(tempResultColumn.getSplitRenderer());
155+
}
156+
renderer = tempResultColumn.getOverflowRenderer();
157+
}
158+
return renderer;
159+
}
160+
}

0 commit comments

Comments
 (0)