From 8c96a3a2d83fbca9e55135a2326cdff8c3b94913 Mon Sep 17 00:00:00 2001 From: Ivo Majic Date: Mon, 1 Jul 2024 22:59:52 +0200 Subject: [PATCH] #682 Fixed an issue with OpenPDF converter manifesting while rendering an ODT into PDF. A problematic ODT would need to have a table containing merged cells for the issue to pop up. --- .../pdf/internal/stylable/StylableTable.java | 51 +++++++++++++++++- .../core/AbstractODFDOMConverterTest.java | 7 +++ .../odfdom/converter/core/Issue682.odt | Bin 0 -> 12802 bytes 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/resources/org/odftoolkit/odfdom/converter/core/Issue682.odt diff --git a/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/main/java/fr/opensagres/odfdom/converter/pdf/internal/stylable/StylableTable.java b/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/main/java/fr/opensagres/odfdom/converter/pdf/internal/stylable/StylableTable.java index 88d3f321b..243145952 100644 --- a/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/main/java/fr/opensagres/odfdom/converter/pdf/internal/stylable/StylableTable.java +++ b/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/main/java/fr/opensagres/odfdom/converter/pdf/internal/stylable/StylableTable.java @@ -26,11 +26,14 @@ import com.lowagie.text.Element; +import com.lowagie.text.pdf.PdfPCell; import fr.opensagres.odfdom.converter.pdf.internal.styles.Style; import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleTableProperties; import fr.opensagres.odfdom.converter.pdf.internal.styles.StyleTableRowProperties; import fr.opensagres.xdocreport.openpdf.extension.ExtendedPdfPTable; +import java.util.Arrays; + public class StylableTable extends ExtendedPdfPTable implements IStylableContainer @@ -45,6 +48,10 @@ public class StylableTable private boolean inTableRow; + // keeps an indication of how many rows are already occupied (by row- or col-spans from the rows above) + private int[] spansFromAbove; + private int spansFromAboveIdx; + private Style currentRowStyle; public StylableTable( StylableDocument ownerDocument, IStylableContainer parent, int numColumns ) @@ -57,6 +64,10 @@ public StylableTable( StylableDocument ownerDocument, IStylableContainer parent, super.setSplitLate( false ); this.ownerDocument = ownerDocument; this.parent = parent; + + this.spansFromAbove = new int[numColumns]; + Arrays.fill(spansFromAbove, 0); + this.spansFromAboveIdx = 0; } public Style getCurrentRowStyle() @@ -74,10 +85,43 @@ public void endTableHeaderRows() inTableHeaderRows = false; } + private void fitCellIntoSpansFromAbove(PdfPCell cell) { + int rowSpan = cell.getRowspan(); + int colSpan = cell.getColspan(); + + // skip all the cell places already spanned from above + while (spansFromAbove[spansFromAboveIdx] > 0){ + if (spansFromAboveIdx < getNumberOfColumns()) { + spansFromAbove[spansFromAboveIdx]--; + spansFromAboveIdx++; + } + if (spansFromAboveIdx >= getNumberOfColumns()) { + // we have no more place in the current row; finish it up, and start a new one + endTableRow(); + beginTableRow(currentRowStyle); + } + } + + for (int col = 0; col < colSpan; col++){ + if (spansFromAbove[spansFromAboveIdx] > 0){ + break; // this is an error situation. cell spans over a cell that already has been spanned from above + } + else { + spansFromAbove[spansFromAboveIdx] = rowSpan - 1; + } + spansFromAboveIdx++; + } + } + + private boolean reachedEndOfRow(){ + return spansFromAboveIdx == getNumberOfColumns(); + } + public void beginTableRow( Style currentRowStyle ) { // beginTableRow/addElement/endTableRow protects before too many/too less cells in a row than declared inTableRow = true; + spansFromAboveIdx = 0; this.currentRowStyle = currentRowStyle; if ( inTableHeaderRows ) { @@ -89,7 +133,7 @@ public void beginTableRow( Style currentRowStyle ) public void endTableRow() { // fill row with empty cells if necessary - while ( currentRowIdx != 0 ) + while ( ! reachedEndOfRow() ) { StylableTableCell cell = new StylableTableCell( ownerDocument, this ); if ( currentRowStyle != null ) @@ -107,9 +151,12 @@ public void addElement( Element element ) { if ( inTableRow ) { + if (element instanceof PdfPCell){ + fitCellIntoSpansFromAbove ((PdfPCell) element); + } super.addElement( element ); } - if ( currentRowIdx == 0 ) + if ( reachedEndOfRow() ) { // row fully filled, end row endTableRow(); diff --git a/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/java/org/odftoolkit/odfdom/converter/core/AbstractODFDOMConverterTest.java b/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/java/org/odftoolkit/odfdom/converter/core/AbstractODFDOMConverterTest.java index dfc5812bb..5f737d568 100644 --- a/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/java/org/odftoolkit/odfdom/converter/core/AbstractODFDOMConverterTest.java +++ b/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/java/org/odftoolkit/odfdom/converter/core/AbstractODFDOMConverterTest.java @@ -125,6 +125,13 @@ public void Issue377() { doGenerate( "Issue377.odt" ); } + + @Test + public void Issue682() + throws Exception + { + doGenerate( "Issue682.odt" ); + } @Test public void ODTBig() diff --git a/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/resources/org/odftoolkit/odfdom/converter/core/Issue682.odt b/thirdparties-extension/fr.opensagres.odfdom.converter.pdf.openpdf/src/test/resources/org/odftoolkit/odfdom/converter/core/Issue682.odt new file mode 100644 index 0000000000000000000000000000000000000000..aec42193ebd418c61597c63ac6148e5cc6208349 GIT binary patch literal 12802 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 literal 0 HcmV?d00001