Skip to content

Commit 8c9f88c

Browse files
author
Vitali Prudnikovich
committed
Fix slow performance of nested flex containers
DEVSIX-6401
1 parent 1f140ca commit 8c9f88c

File tree

3 files changed

+75
-15
lines changed

3 files changed

+75
-15
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,20 @@ This file is part of the iText (R) project.
5858
import com.itextpdf.layout.properties.UnitValue;
5959

6060
import java.util.ArrayList;
61+
import java.util.HashMap;
6162
import java.util.List;
6263
import java.util.Map;
6364
import java.util.Set;
6465

6566
public class FlexContainerRenderer extends DivRenderer {
6667

68+
/* Used for caching purposes in FlexUtil
69+
* We couldn't find the real use case when this map contains more than 1 entry
70+
* but let it still be a map to be on a safe(r) side
71+
* Map mainSize (always width in our case) - hypotheticalCrossSize
72+
*/
73+
private final Map<Float, Float> hypotheticalCrossSizes = new HashMap<>();
74+
6775
private List<List<FlexItemInfo>> lines;
6876

6977
/**
@@ -322,6 +330,14 @@ Rectangle recalculateLayoutBoxBeforeChildLayout(Rectangle layoutBox,
322330
return layoutBoxCopy;
323331
}
324332

333+
void setHypotheticalCrossSize(Float mainSize, Float hypotheticalCrossSize) {
334+
hypotheticalCrossSizes.put(mainSize.floatValue(), hypotheticalCrossSize);
335+
}
336+
337+
Float getHypotheticalCrossSize(Float mainSize) {
338+
return hypotheticalCrossSizes.get(mainSize.floatValue());
339+
}
340+
325341
private FlexItemInfo findFlexItemInfo(AbstractRenderer renderer) {
326342
for (List<FlexItemInfo> line : lines) {
327343
for (FlexItemInfo itemInfo : line) {

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

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public static List<List<FlexItemInfo>> calculateChildrenRectangles(Rectangle fle
9090
FlexContainerRenderer flexContainerRenderer) {
9191
Rectangle layoutBox = flexContainerBBox.clone();
9292
flexContainerRenderer.applyMarginsBordersPaddings(layoutBox, false);
93+
// Currently only width is used in this method
94+
final float layoutBoxWidth = layoutBox.getWidth();
9395

9496
// 9.2. Line Length Determination
9597

@@ -99,9 +101,9 @@ public static List<List<FlexItemInfo>> calculateChildrenRectangles(Rectangle fle
99101
// if that dimension of the flex container is being sized under a min or max-content constraint,
100102
// the available space in that dimension is that constraint;
101103

102-
Float mainSize = flexContainerRenderer.retrieveWidth(layoutBox.getWidth());
104+
Float mainSize = flexContainerRenderer.retrieveWidth(layoutBoxWidth);
103105
if (mainSize == null) {
104-
mainSize = layoutBox.getWidth();
106+
mainSize = layoutBoxWidth;
105107
}
106108
// We need to have crossSize only if its value is definite.
107109
Float crossSize = flexContainerRenderer.retrieveHeight();
@@ -408,20 +410,36 @@ static void resolveFlexibleLengths(List<List<FlexItemCalculationInfo>> lines, fl
408410
static void determineHypotheticalCrossSizeForFlexItems(List<List<FlexItemCalculationInfo>> lines) {
409411
for (List<FlexItemCalculationInfo> line : lines) {
410412
for (FlexItemCalculationInfo info : line) {
411-
UnitValue prevWidth = info.renderer.<UnitValue>replaceOwnProperty(Property.WIDTH,
412-
UnitValue.createPointValue(info.mainSize));
413-
UnitValue prevMinWidth = info.renderer.<UnitValue>replaceOwnProperty(Property.MIN_WIDTH, null);
414-
LayoutResult result = info.renderer.layout(new LayoutContext(
415-
new LayoutArea(0, new Rectangle(AbstractRenderer.INF, AbstractRenderer.INF))));
416-
info.renderer.returnBackOwnProperty(Property.MIN_WIDTH, prevMinWidth);
417-
info.renderer.returnBackOwnProperty(Property.WIDTH, prevWidth);
418-
// Since main size is clamped with min-width, we do expect the result to be full
419-
if (result.getStatus() == LayoutResult.FULL) {
420-
info.hypotheticalCrossSize = info.getInnerCrossSize(result.getOccupiedArea().getBBox().getHeight());
421-
} else {
422-
logger.error(IoLogMessageConstant.FLEX_ITEM_LAYOUT_RESULT_IS_NOT_FULL);
423-
info.hypotheticalCrossSize = 0;
413+
determineHypotheticalCrossSizeForFlexItem(info);
414+
}
415+
}
416+
}
417+
418+
private static void determineHypotheticalCrossSizeForFlexItem(FlexItemCalculationInfo info) {
419+
if (info.renderer instanceof FlexContainerRenderer &&
420+
((FlexContainerRenderer) info.renderer).getHypotheticalCrossSize(info.mainSize) != null) {
421+
// Take from cache
422+
info.hypotheticalCrossSize = ((FlexContainerRenderer) info.renderer)
423+
.getHypotheticalCrossSize(info.mainSize).floatValue();
424+
} else {
425+
UnitValue prevWidth = info.renderer.<UnitValue>replaceOwnProperty(Property.WIDTH,
426+
UnitValue.createPointValue(info.mainSize));
427+
UnitValue prevMinWidth = info.renderer.<UnitValue>replaceOwnProperty(Property.MIN_WIDTH, null);
428+
LayoutResult result = info.renderer.layout(new LayoutContext(
429+
new LayoutArea(0, new Rectangle(AbstractRenderer.INF, AbstractRenderer.INF))));
430+
info.renderer.returnBackOwnProperty(Property.MIN_WIDTH, prevMinWidth);
431+
info.renderer.returnBackOwnProperty(Property.WIDTH, prevWidth);
432+
// Since main size is clamped with min-width, we do expect the result to be full
433+
if (result.getStatus() == LayoutResult.FULL) {
434+
info.hypotheticalCrossSize = info.getInnerCrossSize(result.getOccupiedArea().getBBox().getHeight());
435+
// Cache hypotheticalCrossSize for FlexContainerRenderer
436+
if (info.renderer instanceof FlexContainerRenderer) {
437+
((FlexContainerRenderer) info.renderer).setHypotheticalCrossSize(info.mainSize,
438+
info.hypotheticalCrossSize);
424439
}
440+
} else {
441+
logger.error(IoLogMessageConstant.FLEX_ITEM_LAYOUT_RESULT_IS_NOT_FULL);
442+
info.hypotheticalCrossSize = 0;
425443
}
426444
}
427445
}

layout/src/test/java/com/itextpdf/layout/renderer/FlexContainerRendererTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
2727
import com.itextpdf.layout.borders.SolidBorder;
2828
import com.itextpdf.layout.element.Div;
2929
import com.itextpdf.layout.layout.LayoutArea;
30+
import com.itextpdf.layout.layout.LayoutContext;
3031
import com.itextpdf.layout.layout.LayoutResult;
3132
import com.itextpdf.layout.properties.Property;
3233
import com.itextpdf.layout.properties.UnitValue;
@@ -274,4 +275,29 @@ public void getNextRendererShouldBeOverriddenTest() {
274275

275276
Assert.assertEquals(FlexContainerRenderer.class, flexContainerRenderer.getNextRenderer().getClass());
276277
}
278+
279+
@Test
280+
public void hypotheticalCrossSizeCacheTest() {
281+
FlexContainerRenderer flexRenderer = new FlexContainerRenderer(new Div());
282+
flexRenderer.setProperty(Property.MAX_WIDTH, UnitValue.createPointValue(150));
283+
284+
FlexContainerRenderer flexRendererChild = new FlexContainerRenderer(new Div());
285+
flexRendererChild.setProperty(Property.MAX_WIDTH, UnitValue.createPointValue(150));
286+
287+
DivRenderer divRenderer = new DivRenderer(new Div());
288+
divRenderer.setProperty(Property.WIDTH, UnitValue.createPointValue(125));
289+
290+
flexRendererChild.addChild(divRenderer);
291+
flexRenderer.addChild(flexRendererChild);
292+
293+
// In general it's possible that we might call layout more than once for 1 renderer
294+
flexRenderer.layout(new LayoutContext(
295+
new LayoutArea(0, new Rectangle(100, 0))));
296+
flexRenderer.layout(new LayoutContext(
297+
new LayoutArea(0, new Rectangle(200, 0))));
298+
299+
// Test that hypotheticalCrossSizes can contain more than 1 value
300+
Assert.assertNotNull(flexRendererChild.getHypotheticalCrossSize(125F));
301+
Assert.assertNotNull(flexRendererChild.getHypotheticalCrossSize(150F));
302+
}
277303
}

0 commit comments

Comments
 (0)