@@ -28,11 +28,13 @@ This file is part of the iText (R) project.
28
28
import com .itextpdf .layout .layout .LayoutArea ;
29
29
import com .itextpdf .layout .layout .LayoutContext ;
30
30
import com .itextpdf .layout .layout .LayoutResult ;
31
+ import com .itextpdf .layout .properties .ContinuousContainer ;
31
32
import com .itextpdf .layout .properties .Property ;
32
33
import com .itextpdf .layout .properties .UnitValue ;
33
34
34
35
import java .util .ArrayList ;
35
36
import java .util .List ;
37
+ import java .util .function .BiFunction ;
36
38
37
39
/**
38
40
* Represents a renderer for columns.
@@ -43,6 +45,7 @@ public class MulticolRenderer extends AbstractRenderer {
43
45
private static final float ZERO_DELTA = 0.0001F ;
44
46
45
47
private BlockRenderer elementRenderer ;
48
+ private final HeightEnhancer heightCalculator = new HeightEnhancer ();
46
49
private int columnCount ;
47
50
private float columnWidth ;
48
51
private float approximateHeight ;
@@ -79,29 +82,73 @@ public LayoutResult layout(LayoutContext layoutContext) {
79
82
LayoutResult prelayoutResult = elementRenderer .layout (
80
83
new LayoutContext (new LayoutArea (1 , new Rectangle (columnWidth , INF ))));
81
84
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 () );
83
86
}
84
87
85
88
approximateHeight = prelayoutResult .getOccupiedArea ().getBBox ().getHeight () / columnCount ;
86
89
87
- List < IRenderer > container = balanceContentAndLayoutColumns (layoutContext , actualBBox );
90
+ MulticolLayoutResult layoutResult = balanceContentAndLayoutColumns (layoutContext , actualBBox );
88
91
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
+ }
97
104
}
98
105
106
+ /**
107
+ * {@inheritDoc}
108
+ */
99
109
@ Override
100
110
public IRenderer getNextRenderer () {
101
111
logWarningIfGetNextRendererNotOverridden (MulticolRenderer .class , this .getClass ());
102
112
return new MulticolRenderer ((MulticolContainer ) modelElement );
103
113
}
104
114
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
+
105
152
private float safelyRetrieveFloatProperty (int property ) {
106
153
final Object value = this .<Object >getProperty (property );
107
154
if (value instanceof UnitValue ) {
@@ -113,42 +160,46 @@ private float safelyRetrieveFloatProperty(int property) {
113
160
return 0F ;
114
161
}
115
162
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 ;
120
169
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 ;
125
173
}
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 ;
131
177
}
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 ;
134
181
}
135
- approximateHeight += additionalHeightPerIteration . floatValue () ;
182
+ approximateHeight += additionalHeightPerIteration ;
136
183
}
137
- return container ;
184
+ return result ;
138
185
}
139
186
140
-
141
- private LayoutArea calculateContainerOccupiedArea (LayoutContext layoutContext ) {
187
+ private LayoutArea calculateContainerOccupiedArea (LayoutContext layoutContext , boolean isFull ) {
142
188
LayoutArea area = layoutContext .getArea ().clone ();
143
189
float totalHeight = approximateHeight ;
144
190
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
+ }
146
196
totalHeight += safelyRetrieveFloatProperty (Property .PADDING_TOP );
147
- totalHeight += safelyRetrieveFloatProperty ( Property . MARGIN_BOTTOM );
197
+
148
198
totalHeight += safelyRetrieveFloatProperty (Property .MARGIN_TOP );
149
- totalHeight += safelyRetrieveFloatProperty ( Property . BORDER_BOTTOM );
199
+
150
200
totalHeight += safelyRetrieveFloatProperty (Property .BORDER_TOP );
151
- final float TOP_AND_BOTTOM = 2 ;
201
+ final float TOP_AND_BOTTOM = isFull ? 2 : 1 ;
202
+
152
203
totalHeight += safelyRetrieveFloatProperty (Property .BORDER ) * TOP_AND_BOTTOM ;
153
204
154
205
area .getBBox ().setHeight (totalHeight );
@@ -164,31 +215,94 @@ private BlockRenderer getElementsRenderer() {
164
215
return (BlockRenderer ) getChildRenderers ().get (0 );
165
216
}
166
217
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 ();
172
220
IRenderer renderer = elementRenderer ;
173
221
for (int i = 0 ; i < columnCount && renderer != null ; i ++) {
174
222
LayoutArea tempArea = preLayoutContext .getArea ().clone ();
175
223
tempArea .getBBox ().setWidth (columnWidth );
176
224
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 ()
179
227
.getHeight ());
180
228
181
229
LayoutContext columnContext = new LayoutContext (tempArea , preLayoutContext .getMarginsCollapseInfo (),
182
230
preLayoutContext .getFloatRendererAreas (), preLayoutContext .isClippedHeight ());
183
231
renderer .setProperty (Property .COLLAPSING_MARGINS , false );
184
232
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
+ }
185
238
if (tempResultColumn .getSplitRenderer () == null ) {
186
- container .add (renderer );
239
+ result . getSplitRenderers () .add (renderer );
187
240
} else {
188
- container .add (tempResultColumn .getSplitRenderer ());
241
+ result . getSplitRenderers () .add (tempResultColumn .getSplitRenderer ());
189
242
}
190
243
renderer = tempResultColumn .getOverflowRenderer ();
191
244
}
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
+ }
193
307
}
194
308
}
0 commit comments