@@ -28,11 +28,13 @@ This file is part of the iText (R) project.
2828import com .itextpdf .layout .layout .LayoutArea ;
2929import com .itextpdf .layout .layout .LayoutContext ;
3030import com .itextpdf .layout .layout .LayoutResult ;
31+ import com .itextpdf .layout .properties .ContinuousContainer ;
3132import com .itextpdf .layout .properties .Property ;
3233import com .itextpdf .layout .properties .UnitValue ;
3334
3435import java .util .ArrayList ;
3536import 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