Skip to content

Commit a57e642

Browse files
committed
Fix empty row elimination logic
DEVSIX-6095
1 parent daafb56 commit a57e642

File tree

8 files changed

+101
-58
lines changed

8 files changed

+101
-58
lines changed

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

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -262,15 +262,10 @@ protected void buildBordersArrays(CellRenderer cell, int row, int col, int[] row
262262

263263
} while (j > 0 && rows.size() != nextCellRow &&
264264
(j + (int) rows.get(nextCellRow)[j].getPropertyAsInteger(Property.COLSPAN) != col ||
265-
(int) nextCellRow - rows.get((int) nextCellRow)[j].getPropertyAsInteger(Property.ROWSPAN) + 1 + rowspansToDeduct[j] != row));
265+
(int) nextCellRow - rows.get((int) nextCellRow)[j].getPropertyAsInteger(Property.ROWSPAN) + 1 != row));
266266
// process only valid cells which hasn't been processed yet
267267
if (j >= 0 && nextCellRow != rows.size() && nextCellRow > row) {
268268
CellRenderer nextCell = rows.get(nextCellRow)[j];
269-
nextCell.setProperty(Property.ROWSPAN, ((int) nextCell.getPropertyAsInteger(Property.ROWSPAN)) - rowspansToDeduct[j]);
270-
int nextCellColspan = (int) nextCell.getPropertyAsInteger(Property.COLSPAN);
271-
for (int i = j; i < j + nextCellColspan; i++) {
272-
rowspansToDeduct[i] = 0;
273-
}
274269
buildBordersArrays(nextCell, nextCellRow, true);
275270
}
276271

@@ -301,11 +296,6 @@ protected void buildBordersArrays(CellRenderer cell, int row, int col, int[] row
301296
}
302297
if (nextCellRow != rows.size()) {
303298
CellRenderer nextCell = rows.get(nextCellRow)[col + currCellColspan];
304-
nextCell.setProperty(Property.ROWSPAN, ((int) nextCell.getPropertyAsInteger(Property.ROWSPAN)) - rowspansToDeduct[col + currCellColspan]);
305-
int nextCellColspan = (int) nextCell.getPropertyAsInteger(Property.COLSPAN);
306-
for (int i = col + currCellColspan; i < col + currCellColspan + nextCellColspan; i++) {
307-
rowspansToDeduct[i] = 0;
308-
}
309299
buildBordersArrays(nextCell, nextCellRow, true);
310300
}
311301
}

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

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -155,61 +155,55 @@ public TableBorders(List<CellRenderer[]> rows, int numberOfColumns, Border[] tab
155155
protected abstract float getCellVerticalAddition(float[] indents);
156156
// endregion
157157

158+
/**
159+
* @deprecated Remove rowspansToDeduct parameter which is not used anymore.
160+
*/
161+
@Deprecated
158162
protected abstract void buildBordersArrays(CellRenderer cell, int row, int col, int[] rowspansToDeduct);
159163

160164
protected abstract TableBorders updateBordersOnNewPage(boolean isOriginalNonSplitRenderer, boolean isFooterOrHeader, TableRenderer currentRenderer, TableRenderer headerRenderer, TableRenderer footerRenderer);
161165
// endregion
162166

163167
protected TableBorders processAllBordersAndEmptyRows() {
164168
CellRenderer[] currentRow;
165-
int[] rowspansToDeduct = new int[numberOfColumns];
166169
int numOfRowsToRemove = 0;
167170
if (!rows.isEmpty()) {
168171
for (int row = startRow - largeTableIndexOffset; row <= finishRow - largeTableIndexOffset; row++) {
169172
currentRow = rows.get(row);
170173
boolean hasCells = false;
171174
for (int col = 0; col < numberOfColumns; col++) {
172175
if (null != currentRow[col]) {
173-
int colspan = (int) currentRow[col].getPropertyAsInteger(Property.COLSPAN);
174-
if (rowspansToDeduct[col] > 0) {
175-
int rowspan = (int) currentRow[col].getPropertyAsInteger(Property.ROWSPAN) - rowspansToDeduct[col];
176-
if (rowspan < 1) {
177-
Logger logger = LoggerFactory.getLogger(TableRenderer.class);
178-
logger.warn(IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING);
179-
rowspan = 1;
180-
}
181-
currentRow[col].setProperty(Property.ROWSPAN, rowspan);
182-
if (0 != numOfRowsToRemove) {
183-
removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
184-
row -= numOfRowsToRemove;
185-
numOfRowsToRemove = 0;
186-
}
176+
if (0 != numOfRowsToRemove) {
177+
// Decrease rowspans if necessary
178+
updateRowspanForNextNonEmptyCellInEachColumn(numOfRowsToRemove, row);
179+
180+
// Remove empty rows
181+
removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
182+
row -= numOfRowsToRemove;
183+
numOfRowsToRemove = 0;
187184
}
188-
buildBordersArrays(currentRow[col], row, col, rowspansToDeduct);
185+
186+
buildBordersArrays(currentRow[col], row, col, null);
189187
hasCells = true;
190-
for (int i = 0; i < colspan; i++) {
191-
rowspansToDeduct[col + i] = 0;
192-
}
188+
int colspan = (int) currentRow[col].getPropertyAsInteger(Property.COLSPAN);
193189
col += colspan - 1;
194190
} else {
195191
if (horizontalBorders.get(row).size() <= col) {
196192
horizontalBorders.get(row).add(null);
197193
}
198194
}
199195
}
196+
200197
if (!hasCells) {
201198
if (row == rows.size() - 1) {
202-
removeRows(row - rowspansToDeduct[0], rowspansToDeduct[0]);
199+
removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
203200
// delete current row
204-
rows.remove(row - rowspansToDeduct[0]);
201+
rows.remove(row - numOfRowsToRemove);
205202
setFinishRow(finishRow - 1);
206203

207204
Logger logger = LoggerFactory.getLogger(TableRenderer.class);
208205
logger.warn(IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE);
209206
} else {
210-
for (int i = 0; i < numberOfColumns; i++) {
211-
rowspansToDeduct[i]++;
212-
}
213207
numOfRowsToRemove++;
214208
}
215209
}
@@ -221,17 +215,6 @@ protected TableBorders processAllBordersAndEmptyRows() {
221215
return this;
222216
}
223217

224-
private void removeRows(int startRow, int numOfRows) {
225-
for (int row = startRow; row < startRow + numOfRows; row++) {
226-
rows.remove(startRow);
227-
horizontalBorders.remove(startRow + 1);
228-
for (int j = 0; j <= numberOfColumns; j++) {
229-
verticalBorders.get(j).remove(startRow + 1);
230-
}
231-
}
232-
setFinishRow(finishRow - numOfRows);
233-
}
234-
235218
// region init
236219
protected TableBorders initializeBorders() {
237220
List<Border> tempBorders;
@@ -415,4 +398,62 @@ public float[] getCellBorderIndents(int row, int col, int rowspan, int colspan)
415398
return indents;
416399
}
417400
// endregion
401+
402+
private void removeRows(int startRow, int numOfRows) {
403+
for (int row = startRow; row < startRow + numOfRows; row++) {
404+
rows.remove(startRow);
405+
horizontalBorders.remove(startRow + 1);
406+
for (int j = 0; j <= numberOfColumns; j++) {
407+
verticalBorders.get(j).remove(startRow + 1);
408+
}
409+
}
410+
setFinishRow(finishRow - numOfRows);
411+
}
412+
413+
private void updateRowspanForNextNonEmptyCellInEachColumn(int numOfRowsToRemove, int row) {
414+
// We go by columns in a current row which is not empty. For each column we look for
415+
// a non-empty cell going up by rows (going down in a table). For each such cell we
416+
// collect data to be able to analyze its rowspan.
417+
418+
// Iterate by columns
419+
int c = 0;
420+
while (c < numberOfColumns) {
421+
int r = row;
422+
CellRenderer[] cr = null;
423+
// Look for non-empty cell in a column
424+
while (r < rows.size() && (cr == null || cr[c] == null)) {
425+
cr = rows.get(r);
426+
++r;
427+
}
428+
429+
// Found a cell
430+
if (cr != null && cr[c] != null) {
431+
CellRenderer cell = cr[c];
432+
final int origRowspan = (int) cell.getPropertyAsInteger(Property.ROWSPAN);
433+
int spansToRestore = 0;
434+
// Here we analyze whether current cell's rowspan touches a non-empty row before
435+
// numOfRowsToRemove. If it doesn't touch it we will need to 'restore' a few
436+
// rowspans which is a difference between the current (non-empty) row and the row
437+
// where we found non-empty cell for this column
438+
if (row - numOfRowsToRemove < r - origRowspan) {
439+
spansToRestore = r - row - 1;
440+
}
441+
442+
int rowspan = origRowspan;
443+
rowspan = rowspan - numOfRowsToRemove;
444+
if (rowspan < 1) {
445+
rowspan = 1;
446+
}
447+
rowspan += spansToRestore;
448+
rowspan = Math.min(rowspan, origRowspan);
449+
450+
cell.setProperty(Property.ROWSPAN, rowspan);
451+
452+
final int colspan = (int) cell.getPropertyAsInteger(Property.COLSPAN);
453+
c += colspan;
454+
} else {
455+
++c;
456+
}
457+
}
458+
}
418459
}

layout/src/test/java/com/itextpdf/layout/TableTest.java

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,9 +2048,6 @@ public void tableWithEmptyLastRowTest() throws IOException, InterruptedException
20482048
}
20492049

20502050
@Test
2051-
@LogMessages(messages = {
2052-
@LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING)
2053-
})
20542051
public void tableWithEmptyRowsBetweenFullRowsTest() throws IOException, InterruptedException {
20552052
String testName = "tableWithEmptyRowsBetweenFullRowsTest.pdf";
20562053
String outFileName = destinationFolder + testName;
@@ -2073,7 +2070,6 @@ public void tableWithEmptyRowsBetweenFullRowsTest() throws IOException, Interrup
20732070

20742071
@Test
20752072
@LogMessages(messages = {
2076-
@LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 2),
20772073
@LogMessage(messageTemplate = IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE)
20782074
})
20792075
public void tableWithEmptyRowAfterJustOneCellTest() throws IOException, InterruptedException {
@@ -2099,7 +2095,6 @@ public void tableWithEmptyRowAfterJustOneCellTest() throws IOException, Interrup
20992095

21002096
@Test
21012097
@LogMessages(messages = {
2102-
@LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 39),
21032098
@LogMessage(messageTemplate = IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE)
21042099
})
21052100
public void tableWithAlternatingRowsTest() throws IOException, InterruptedException {
@@ -2146,9 +2141,30 @@ public void coloredTableWithColoredCellsTest() throws IOException, InterruptedEx
21462141
}
21472142

21482143
@Test
2149-
@LogMessages(messages = {
2150-
@LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 2)
2151-
})
2144+
public void tableWithEmptyRowsAndSpansTest() throws IOException, InterruptedException {
2145+
String testName = "tableWithEmptyRowsAndSpansTest.pdf";
2146+
String outFileName = destinationFolder + testName;
2147+
String cmpFileName = sourceFolder + "cmp_" + testName;
2148+
2149+
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
2150+
Document doc = new Document(pdfDoc);
2151+
2152+
Table table = new Table(UnitValue.createPercentArray(new float[]{30, 30, 30}));
2153+
table.addCell(new Cell().add(new Paragraph("Hello")));
2154+
table.addCell(new Cell().add(new Paragraph("Lovely")));
2155+
table.addCell(new Cell().add(new Paragraph("World")));
2156+
startSeveralEmptyRows(table);
2157+
table.addCell(new Cell(2, 2).add(new Paragraph("Hello")));
2158+
table.addCell(new Cell().add(new Paragraph("Lovely")));
2159+
table.addCell(new Cell().add(new Paragraph("World")));
2160+
2161+
doc.add(table);
2162+
2163+
doc.close();
2164+
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, testName + "_diff"));
2165+
}
2166+
2167+
@Test
21522168
public void tableWithEmptyRowsAndSeparatedBordersTest() throws IOException, InterruptedException {
21532169
String testName = "tableWithEmptyRowsAndSeparatedBordersTest.pdf";
21542170
String outFileName = destinationFolder + testName;
@@ -2171,10 +2187,6 @@ public void tableWithEmptyRowsAndSeparatedBordersTest() throws IOException, Inte
21712187
}
21722188

21732189
@Test
2174-
// TODO DEVSIX-6020:Border-collapsing doesn't work in case startNewRow has been called
2175-
@LogMessages(messages = {
2176-
@LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING)
2177-
})
21782190
public void tableWithCollapsedBordersTest() throws IOException, InterruptedException {
21792191
String testName = "tableWithCollapsedBordersTest.pdf";
21802192
String outFileName = destinationFolder + testName;

0 commit comments

Comments
 (0)