Skip to content

Commit a22302b

Browse files
author
dmitry.radchuk
committed
Add split/overflow renderer support for MulticolRenderer
DEVSIX-7584
1 parent c6b1a6d commit a22302b

22 files changed

+525
-51
lines changed

layout/src/main/java/com/itextpdf/layout/minmaxwidth/MinMaxWidthUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static float getInfWidth() {
4949
return max;
5050
}
5151

52-
private static float getInfHeight() { return 1e6f; }
52+
public static float getInfHeight() { return 1e6f; }
5353

5454
public static boolean isEqual(double x, double y) {
5555
return Math.abs(x - y) < eps;

layout/src/main/java/com/itextpdf/layout/properties/ContinuousContainer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public static void clearPropertiesFromOverFlowRenderer(IPropertyContainer overFl
8080
*
8181
* @param blockRenderer the renderer that is used to set up continuous container.
8282
*/
83-
public static void setupContinuousContainerIfNeeded(BlockRenderer blockRenderer) {
83+
public static void setupContinuousContainerIfNeeded(AbstractRenderer blockRenderer) {
8484
if (Boolean.TRUE.equals(blockRenderer.<Boolean>getProperty(Property.TREAT_AS_CONTINUOUS_CONTAINER))) {
8585
if (!blockRenderer.hasProperty(Property.TREAT_AS_CONTINUOUS_CONTAINER_RESULT)) {
8686
final ContinuousContainer continuousContainer = new ContinuousContainer(blockRenderer);

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

Lines changed: 157 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ This file is part of the iText (R) project.
2828
import com.itextpdf.layout.layout.LayoutArea;
2929
import com.itextpdf.layout.layout.LayoutContext;
3030
import com.itextpdf.layout.layout.LayoutResult;
31+
import com.itextpdf.layout.properties.ContinuousContainer;
3132
import com.itextpdf.layout.properties.Property;
3233
import com.itextpdf.layout.properties.UnitValue;
3334

3435
import java.util.ArrayList;
3536
import java.util.List;
37+
import java.util.function.BiFunction;
3638

3739
/**
3840
* Represents a renderer for columns.
@@ -43,6 +45,7 @@ public class MulticolRenderer extends AbstractRenderer {
4345
private static final float ZERO_DELTA = 0.0001F;
4446

4547
private BlockRenderer elementRenderer;
48+
private final HeightEnhancer heightCalculator = new HeightEnhancer();
4649
private int columnCount;
4750
private float columnWidth;
4851
private float approximateHeight;
@@ -79,29 +82,73 @@ public LayoutResult layout(LayoutContext layoutContext) {
7982
LayoutResult prelayoutResult = elementRenderer.layout(
8083
new LayoutContext(new LayoutArea(1, new Rectangle(columnWidth, INF))));
8184
if (prelayoutResult.getStatus() != LayoutResult.FULL) {
82-
return new LayoutResult(LayoutResult.NOTHING, null, null, this, elementRenderer);
85+
return new LayoutResult(LayoutResult.NOTHING, null, null, this, prelayoutResult.getCauseOfNothing());
8386
}
8487

8588
approximateHeight = prelayoutResult.getOccupiedArea().getBBox().getHeight() / columnCount;
8689

87-
List<IRenderer> container = balanceContentAndLayoutColumns(layoutContext, actualBBox);
90+
MulticolLayoutResult layoutResult = balanceContentAndLayoutColumns(layoutContext, actualBBox);
8891

89-
this.occupiedArea = calculateContainerOccupiedArea(layoutContext);
90-
this.setChildRenderers(container);
91-
LayoutResult result = new LayoutResult(LayoutResult.FULL, this.occupiedArea, this, null);
92-
93-
// process some properties (keepTogether, margin collapsing), area breaks, adding height
94-
// Check what we do at the end of BlockRenderer
95-
96-
return result;
92+
if (layoutResult.getSplitRenderers().isEmpty()) {
93+
return new LayoutResult(LayoutResult.NOTHING, null, null, this, layoutResult.getCauseOfNothing());
94+
} else if (layoutResult.getOverflowRenderer() == null) {
95+
this.setChildRenderers(layoutResult.getSplitRenderers());
96+
this.occupiedArea = calculateContainerOccupiedArea(layoutContext, true);
97+
return new LayoutResult(LayoutResult.FULL, this.occupiedArea, this, null);
98+
} else {
99+
this.occupiedArea = calculateContainerOccupiedArea(layoutContext, false);
100+
return new LayoutResult(LayoutResult.PARTIAL, this.occupiedArea,
101+
createSplitRenderer(layoutResult.getSplitRenderers()),
102+
createOverflowRenderer(layoutResult.getOverflowRenderer()));
103+
}
97104
}
98105

106+
/**
107+
* {@inheritDoc}
108+
*/
99109
@Override
100110
public IRenderer getNextRenderer() {
101111
logWarningIfGetNextRendererNotOverridden(MulticolRenderer.class, this.getClass());
102112
return new MulticolRenderer((MulticolContainer) modelElement);
103113
}
104114

115+
116+
/**
117+
* Creates a split renderer.
118+
*
119+
* @param children children of the split renderer
120+
*
121+
* @return a new {@link AbstractRenderer} instance
122+
*/
123+
protected AbstractRenderer createSplitRenderer(List<IRenderer> children) {
124+
AbstractRenderer splitRenderer = (AbstractRenderer) getNextRenderer();
125+
splitRenderer.parent = parent;
126+
splitRenderer.modelElement = modelElement;
127+
splitRenderer.occupiedArea = occupiedArea;
128+
splitRenderer.isLastRendererForModelElement = false;
129+
splitRenderer.setChildRenderers(children);
130+
splitRenderer.addAllProperties(getOwnProperties());
131+
ContinuousContainer.setupContinuousContainerIfNeeded(splitRenderer);
132+
return splitRenderer;
133+
}
134+
135+
/**
136+
* Creates an overflow renderer.
137+
*
138+
* @return a new {@link AbstractRenderer} instance
139+
*/
140+
protected AbstractRenderer createOverflowRenderer(IRenderer overflowedContentRenderer) {
141+
AbstractRenderer overflowRenderer = (AbstractRenderer) getNextRenderer();
142+
overflowRenderer.parent = parent;
143+
overflowRenderer.modelElement = modelElement;
144+
overflowRenderer.addAllProperties(getOwnProperties());
145+
List<IRenderer> children = new ArrayList<>(1);
146+
children.add(overflowedContentRenderer);
147+
overflowRenderer.setChildRenderers(children);
148+
ContinuousContainer.clearPropertiesFromOverFlowRenderer(overflowRenderer);
149+
return overflowRenderer;
150+
}
151+
105152
private float safelyRetrieveFloatProperty(int property) {
106153
final Object value = this.<Object>getProperty(property);
107154
if (value instanceof UnitValue) {
@@ -113,42 +160,46 @@ private float safelyRetrieveFloatProperty(int property) {
113160
return 0F;
114161
}
115162

116-
private List<IRenderer> balanceContentAndLayoutColumns(LayoutContext prelayoutContext, Rectangle actualBBox) {
117-
Float additionalHeightPerIteration = null;
118-
final List<IRenderer> container = new ArrayList<>();
119-
int counter = MAX_RELAYOUT_COUNT;
163+
private MulticolLayoutResult balanceContentAndLayoutColumns(LayoutContext prelayoutContext, Rectangle actualBbox) {
164+
float additionalHeightPerIteration;
165+
MulticolLayoutResult result = new MulticolLayoutResult();
166+
int counter = MAX_RELAYOUT_COUNT + 1;
167+
float maxHeight = actualBbox.getHeight();
168+
boolean isLastLayout = false;
120169
while (counter-- > 0) {
121-
final IRenderer overflowRenderer = layoutColumnsAndReturnOverflowRenderer(prelayoutContext, container,
122-
actualBBox);
123-
if (overflowRenderer == null) {
124-
return container;
170+
if (approximateHeight > maxHeight) {
171+
isLastLayout = true;
172+
approximateHeight = maxHeight;
125173
}
126-
if (additionalHeightPerIteration == null) {
127-
LayoutResult overflowResult = overflowRenderer.layout(
128-
new LayoutContext(new LayoutArea(1, new Rectangle(columnWidth, INF))));
129-
additionalHeightPerIteration =
130-
overflowResult.getOccupiedArea().getBBox().getHeight() / MAX_RELAYOUT_COUNT;
174+
result = layoutColumnsAndReturnOverflowRenderer(prelayoutContext, actualBbox);
175+
if (result.getOverflowRenderer() == null || isLastLayout) {
176+
return result;
131177
}
132-
if (Math.abs(additionalHeightPerIteration.floatValue()) <= ZERO_DELTA) {
133-
return container;
178+
additionalHeightPerIteration = heightCalculator.apply(this, result).floatValue();
179+
if (Math.abs(additionalHeightPerIteration) <= ZERO_DELTA) {
180+
return result;
134181
}
135-
approximateHeight += additionalHeightPerIteration.floatValue();
182+
approximateHeight += additionalHeightPerIteration;
136183
}
137-
return container;
184+
return result;
138185
}
139186

140-
141-
private LayoutArea calculateContainerOccupiedArea(LayoutContext layoutContext) {
187+
private LayoutArea calculateContainerOccupiedArea(LayoutContext layoutContext, boolean isFull) {
142188
LayoutArea area = layoutContext.getArea().clone();
143189
float totalHeight = approximateHeight;
144190

145-
totalHeight += safelyRetrieveFloatProperty(Property.PADDING_BOTTOM);
191+
if (isFull) {
192+
totalHeight += safelyRetrieveFloatProperty(Property.PADDING_BOTTOM);
193+
totalHeight += safelyRetrieveFloatProperty(Property.MARGIN_BOTTOM);
194+
totalHeight += safelyRetrieveFloatProperty(Property.BORDER_BOTTOM);
195+
}
146196
totalHeight += safelyRetrieveFloatProperty(Property.PADDING_TOP);
147-
totalHeight += safelyRetrieveFloatProperty(Property.MARGIN_BOTTOM);
197+
148198
totalHeight += safelyRetrieveFloatProperty(Property.MARGIN_TOP);
149-
totalHeight += safelyRetrieveFloatProperty(Property.BORDER_BOTTOM);
199+
150200
totalHeight += safelyRetrieveFloatProperty(Property.BORDER_TOP);
151-
final float TOP_AND_BOTTOM = 2;
201+
final float TOP_AND_BOTTOM = isFull ? 2 : 1;
202+
152203
totalHeight += safelyRetrieveFloatProperty(Property.BORDER) * TOP_AND_BOTTOM;
153204

154205
area.getBBox().setHeight(totalHeight);
@@ -164,31 +215,94 @@ private BlockRenderer getElementsRenderer() {
164215
return (BlockRenderer) getChildRenderers().get(0);
165216
}
166217

167-
private IRenderer layoutColumnsAndReturnOverflowRenderer(LayoutContext preLayoutContext,
168-
List<IRenderer> container, Rectangle actualBBox) {
169-
container.clear();
170-
171-
final Rectangle initialBBox = actualBBox.clone();
218+
private MulticolLayoutResult layoutColumnsAndReturnOverflowRenderer(LayoutContext preLayoutContext, Rectangle actualBBox) {
219+
MulticolLayoutResult result = new MulticolLayoutResult();
172220
IRenderer renderer = elementRenderer;
173221
for (int i = 0; i < columnCount && renderer != null; i++) {
174222
LayoutArea tempArea = preLayoutContext.getArea().clone();
175223
tempArea.getBBox().setWidth(columnWidth);
176224
tempArea.getBBox().setHeight(approximateHeight);
177-
tempArea.getBBox().setX(initialBBox.getX() + columnWidth * i);
178-
tempArea.getBBox().setY(initialBBox.getY() + initialBBox.getHeight() - tempArea.getBBox()
225+
tempArea.getBBox().setX(actualBBox.getX() + columnWidth * i);
226+
tempArea.getBBox().setY(actualBBox.getY() + actualBBox.getHeight() - tempArea.getBBox()
179227
.getHeight());
180228

181229
LayoutContext columnContext = new LayoutContext(tempArea, preLayoutContext.getMarginsCollapseInfo(),
182230
preLayoutContext.getFloatRendererAreas(), preLayoutContext.isClippedHeight());
183231
renderer.setProperty(Property.COLLAPSING_MARGINS, false);
184232
LayoutResult tempResultColumn = renderer.layout(columnContext);
233+
if (tempResultColumn.getStatus() == LayoutResult.NOTHING) {
234+
result.setOverflowRenderer((AbstractRenderer) renderer);
235+
result.setCauseOfNothing(tempResultColumn.getCauseOfNothing());
236+
return result;
237+
}
185238
if (tempResultColumn.getSplitRenderer() == null) {
186-
container.add(renderer);
239+
result.getSplitRenderers().add(renderer);
187240
} else {
188-
container.add(tempResultColumn.getSplitRenderer());
241+
result.getSplitRenderers().add(tempResultColumn.getSplitRenderer());
189242
}
190243
renderer = tempResultColumn.getOverflowRenderer();
191244
}
192-
return renderer;
245+
result.setOverflowRenderer((AbstractRenderer)renderer);
246+
return result;
247+
}
248+
249+
250+
/**
251+
* Represents result of one iteration of MulticolRenderer layouting
252+
* It contains split renderers which were lauded on a given height and overflow renderer
253+
* for which height should be increased, so it can be lauded.
254+
*/
255+
private static class MulticolLayoutResult {
256+
private List<IRenderer> splitRenderers = new ArrayList<>();
257+
private AbstractRenderer overflowRenderer;
258+
private IRenderer causeOfNothing;
259+
260+
public List<IRenderer> getSplitRenderers() {
261+
return splitRenderers;
262+
}
263+
264+
public AbstractRenderer getOverflowRenderer() {
265+
return overflowRenderer;
266+
}
267+
268+
public IRenderer getCauseOfNothing() {
269+
return causeOfNothing;
270+
}
271+
272+
public void setOverflowRenderer(AbstractRenderer overflowRenderer) {
273+
this.overflowRenderer = overflowRenderer;
274+
}
275+
276+
public void setCauseOfNothing(IRenderer causeOfNothing) {
277+
this.causeOfNothing = causeOfNothing;
278+
}
279+
}
280+
281+
/**
282+
* Class which used for additional height calculation
283+
*/
284+
private static class HeightEnhancer {
285+
private Float height = null;
286+
287+
/**
288+
* Calculate height, by which current height of given {@code MulticolRenderer} should be increased so
289+
* {@code MulticolLayoutResult#getOverflowRenderer} could be lauded
290+
*
291+
* @param renderer multicol renderer for which height needs to be increased
292+
* @param result result of one iteration of {@code MulticolRenderer} layouting
293+
* @return height by which current height of given multicol renderer should be increased
294+
*/
295+
public Float apply(MulticolRenderer renderer, MulticolLayoutResult result) {
296+
if (height != null) {
297+
return height;
298+
}
299+
if (result.getOverflowRenderer() == null) {
300+
return 0.0f;
301+
}
302+
LayoutResult overflowResult = result.getOverflowRenderer().layout(
303+
new LayoutContext(new LayoutArea(1, new Rectangle(renderer.columnWidth, INF))));
304+
height = overflowResult.getOccupiedArea().getBBox().getHeight() / MAX_RELAYOUT_COUNT;
305+
return height;
306+
}
193307
}
194308
}

0 commit comments

Comments
 (0)