diff --git a/src/main/java/org/vandeseer/easytable/RepeatedHeaderTableDrawer.java b/src/main/java/org/vandeseer/easytable/RepeatedHeaderTableDrawer.java index 175bf5f..1a25a48 100644 --- a/src/main/java/org/vandeseer/easytable/RepeatedHeaderTableDrawer.java +++ b/src/main/java/org/vandeseer/easytable/RepeatedHeaderTableDrawer.java @@ -1,51 +1,45 @@ package org.vandeseer.easytable; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.experimental.SuperBuilder; +import java.awt.geom.Point2D; +import java.io.IOException; +import java.util.function.Supplier; + import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.vandeseer.easytable.structure.Row; -import java.awt.geom.Point2D; -import java.io.IOException; -import java.util.Queue; -import java.util.function.Supplier; +import lombok.Builder; +import lombok.experimental.SuperBuilder; @SuperBuilder public class RepeatedHeaderTableDrawer extends TableDrawer { - @Builder.Default - private int numberOfRowsToRepeat = 1; - - private Float headerHeight; - - @Getter(value = AccessLevel.NONE) - private boolean startTableInNewPage; - - @Override - protected void drawPage(PageData pageData) { - if (pageData.firstRowOnPage != 0) { - float adaption = 0; - for (int i = 0; i < numberOfRowsToRepeat; i++) { - adaption += table.getRows().get(i).getHeight(); - Point2D.Float startPoint = new Point2D.Float(this.startX, this.startY + calculateHeightForFirstRows() - adaption); - drawRow(startPoint, table.getRows().get(i), i, (drawer, drawingContext) -> { - drawer.drawBackground(drawingContext); - drawer.drawContent(drawingContext); - drawer.drawBorders(drawingContext); - }); - } - } - - drawerList.forEach(drawer -> - drawWithFunction(pageData, new Point2D.Float(this.startX, this.startY), drawer) - ); - } - + @Builder.Default + private int numberOfRowsToRepeat = 1; + + private Float headerHeight; + + @Override + protected void drawPage(PageData pageData) { + if (pageData.firstRowOnPage != 0) { + float adaption = 0; + for (int i = 0; i < numberOfRowsToRepeat; i++) { + adaption += table.getRows().get(i).getHeight(); + Point2D.Float startPoint = new Point2D.Float(this.startX, + this.startY + calculateHeightForFirstRows() - adaption); + drawRow(startPoint, table.getRows().get(i), i, (drawer, drawingContext) -> { + drawer.drawBackground(drawingContext); + drawer.drawContent(drawingContext); + drawer.drawBorders(drawingContext); + }); + } + } + + drawerList.forEach(drawer -> drawWithFunction(pageData, new Point2D.Float(this.startX, this.startY), drawer)); + } + @Override - protected Queue computeRowsOnPagesWithNewPageStartOf(float yOffsetOnNewPage) { + protected void determinePageToStartTable(float yOffsetOnNewPage) { float minimumRowsToFitHeight = 0; int minimumRowsToFit = table.getRows().size() > numberOfRowsToRepeat ? numberOfRowsToRepeat + 1 : numberOfRowsToRepeat; @@ -57,39 +51,27 @@ protected Queue computeRowsOnPagesWithNewPageStartOf(float yOffsetOnNe startY = yOffsetOnNewPage + calculateHeightForFirstRows(); startTableInNewPage = true; } - return super.computeRowsOnPagesWithNewPageStartOf(yOffsetOnNewPage); } @Override - protected PDPage determinePageToDraw(int index, PDDocument document, Supplier pageSupplier) { - final PDPage pageToDrawOn; - if ((index == 0 && startTableInNewPage) || index > 0 || document.getNumberOfPages() == 0) { - startTableInNewPage = false; - pageToDrawOn = pageSupplier.get(); - document.addPage(pageToDrawOn); - } else - pageToDrawOn = document.getPage(document.getNumberOfPages() - 1); - return pageToDrawOn; + public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) + throws IOException { + super.draw(documentSupplier, pageSupplier, yOffset + calculateHeightForFirstRows()); } - @Override - public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) throws IOException { - super.draw(documentSupplier, pageSupplier, yOffset + calculateHeightForFirstRows()); - } - - private float calculateHeightForFirstRows() { - if (headerHeight != null) { - return headerHeight; - } - - float height = 0; - for (int i = 0; i < numberOfRowsToRepeat; i++) { - height += table.getRows().get(i).getHeight(); - } - - // Cache and return - headerHeight = height; - return height; - } + private float calculateHeightForFirstRows() { + if (headerHeight != null) { + return headerHeight; + } + + float height = 0; + for (int i = 0; i < numberOfRowsToRepeat; i++) { + height += table.getRows().get(i).getHeight(); + } + + // Cache and return + headerHeight = height; + return height; + } } diff --git a/src/main/java/org/vandeseer/easytable/TableDrawer.java b/src/main/java/org/vandeseer/easytable/TableDrawer.java index 5ef4070..33786d6 100644 --- a/src/main/java/org/vandeseer/easytable/TableDrawer.java +++ b/src/main/java/org/vandeseer/easytable/TableDrawer.java @@ -1,5 +1,6 @@ package org.vandeseer.easytable; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -54,6 +55,13 @@ public class TableDrawer { @Accessors(chain = true, fluent = true) protected boolean compress; + @Getter + protected PDPage tableStartPage; + + @Getter (AccessLevel.NONE) + protected boolean startTableInNewPage; + + protected final List> drawerList = new LinkedList<>(); { this.drawerList.add((drawer, drawingContext) -> { @@ -96,7 +104,7 @@ protected Queue computeRowsOnPagesWithNewPageStartOf(float yOffsetOnNe if (isRowTooHighToBeDrawnOnPage(row, yOffsetOnNewPage)) { throw new RowIsTooHighException("There is a row that is too high to be drawn on a single page"); } - + if (isNotDrawableOnPage(y, row)) { dataForPages.add(new PageData(firstRowOnPage, lastRowOnPage)); y = yOffsetOnNewPage; @@ -116,16 +124,32 @@ protected Queue computeRowsOnPagesWithNewPageStartOf(float yOffsetOnNe private boolean isRowTooHighToBeDrawnOnPage(Row row, float yOffsetOnNewPage) { return row.getHeight() > (yOffsetOnNewPage - endY); } + + protected void determinePageToStartTable(float yOffsetOnNewPage) { + if (startY - table.getRows().get(0).getHeight() < endY) { + startY = yOffsetOnNewPage; + startTableInNewPage = true; + } + } public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) throws IOException { final PDDocument document = documentSupplier.get(); // We create one throwaway page to be able to calculate the page data upfront float startOnNewPage = pageSupplier.get().getMediaBox().getHeight() - yOffset; + determinePageToStartTable(startOnNewPage); final Queue pageDataQueue = computeRowsOnPagesWithNewPageStartOf(startOnNewPage); for (int i = 0; !pageDataQueue.isEmpty(); i++) { final PDPage pageToDrawOn = determinePageToDraw(i, document, pageSupplier); + + if ((i == 0 && startTableInNewPage) || i > 0 || document.getNumberOfPages() == 0) { + startTableInNewPage = false; + } + + if (i == 0) { + tableStartPage = pageToDrawOn; + } try (final PDPageContentStream newPageContentStream = new PDPageContentStream(document, pageToDrawOn, APPEND, compress)) { this.contentStream(newPageContentStream) @@ -139,11 +163,13 @@ public void draw(Supplier documentSupplier, Supplier pageSup protected PDPage determinePageToDraw(int index, PDDocument document, Supplier pageSupplier) { final PDPage pageToDrawOn; - if (index > 0 || document.getNumberOfPages() == 0) { + + if ((index == 0 && startTableInNewPage) || index > 0 || document.getNumberOfPages() == 0) { pageToDrawOn = pageSupplier.get(); document.addPage(pageToDrawOn); - } else - pageToDrawOn = document.getPage(document.getNumberOfPages() - 1); + } else { + pageToDrawOn = document.getPage(document.getNumberOfPages() - 1); + } return pageToDrawOn; } diff --git a/src/test/java/org/vandeseer/integrationtest/StartPageTest.java b/src/test/java/org/vandeseer/integrationtest/StartPageTest.java new file mode 100644 index 0000000..28f9dbd --- /dev/null +++ b/src/test/java/org/vandeseer/integrationtest/StartPageTest.java @@ -0,0 +1,106 @@ +package org.vandeseer.integrationtest; + +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.assertEquals; +import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; +import static org.vandeseer.TestUtils.getActualPdfFor; +import static org.vandeseer.TestUtils.getExpectedPdfFor; +import static org.apache.pdfbox.pdmodel.font.PDType1Font.HELVETICA; + +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.junit.Before; +import org.junit.Test; +import org.vandeseer.TestUtils; +import org.vandeseer.easytable.TableDrawer; +import org.vandeseer.easytable.structure.Row; +import org.vandeseer.easytable.structure.Table; +import org.vandeseer.easytable.structure.Table.TableBuilder; +import org.vandeseer.easytable.structure.cell.TextCell; + +import de.redsix.pdfcompare.CompareResult; +import de.redsix.pdfcompare.PdfComparator; + +public class StartPageTest { + + private static final String FULL_TABLE_EXISTING_PAGE_FILE_NAME = "fullTableExistingPage.pdf"; + private static final String START_TABLE_EXISTING_PAGE_FILE_NAME = "startTableExistingPage.pdf"; + private static final String START_TABLE_NEW_PAGE_FILE_NAME = "startTableNewPage.pdf"; + + private Table table; + + private PDDocument document; + + private PDPage currentPage; + + private TableDrawer drawer; + + @Before + public void before() throws IOException { + TestUtils.assertRegressionFolderExists(); + + document = new PDDocument(); + currentPage = new PDPage(PDRectangle.A4); + document.addPage(currentPage); + + try (final PDPageContentStream content = new PDPageContentStream(document, currentPage, APPEND, false)) { + content.beginText(); + content.setFont(PDType1Font.TIMES_ROMAN, 15); + content.newLineAtOffset(50, 500); + content.showText("This line is added to ensure table is drawn on new page when space not available."); + content.endText(); + content.close(); + + TableBuilder builder = Table.builder().addColumnsOfWidth(150, 150, 150).fontSize(25).font(HELVETICA) + .padding(5).borderWidth(1) + .addRow(Row.builder().add(TextCell.builder().text("Header").build()) + .add(TextCell.builder().text("of").build()).add(TextCell.builder().text("Table").build()) + .build()); + + for (int i = 1; i < 5; i++) { + builder.addRow(Row.builder().add(TextCell.builder().text("Row " + i).build()) + .add(TextCell.builder().text("of").build()).add(TextCell.builder().text("Table").build()) + .build()).build(); + } + table = builder.build(); + } + } + + private void createTable(float startY, String outputFileName) throws IOException { + + drawer = TableDrawer.builder().table(table).startX(50f).startY(startY).endY(50F).build(); + + drawer.draw(() -> document, () -> new PDPage(PDRectangle.A4), 50f); + + document.save(TestUtils.TARGET_FOLDER + "/" + outputFileName); + document.close(); + + final CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(outputFileName), + getActualPdfFor(outputFileName)).compare(); + + assertTrue(compareResult.isEqual()); + } + + @Test + public void createCompleteTableInExistingPage() throws IOException { + createTable(200f, FULL_TABLE_EXISTING_PAGE_FILE_NAME); + assertEquals(currentPage, drawer.getTableStartPage()); + } + + @Test + public void createSplitTableStartInExistingPage() throws IOException { + createTable(120f, START_TABLE_EXISTING_PAGE_FILE_NAME); + assertEquals(currentPage, drawer.getTableStartPage()); + } + + @Test + public void createTableStartInNewPage() throws IOException { + createTable(70f, START_TABLE_NEW_PAGE_FILE_NAME); + assertEquals(document.getPage(document.getNumberOfPages() - 1), drawer.getTableStartPage()); + } +} diff --git a/src/test/reference/fullTableExistingPage.pdf b/src/test/reference/fullTableExistingPage.pdf new file mode 100644 index 0000000..8327261 Binary files /dev/null and b/src/test/reference/fullTableExistingPage.pdf differ diff --git a/src/test/reference/startTableExistingPage.pdf b/src/test/reference/startTableExistingPage.pdf new file mode 100644 index 0000000..b984a62 Binary files /dev/null and b/src/test/reference/startTableExistingPage.pdf differ diff --git a/src/test/reference/startTableNewPage.pdf b/src/test/reference/startTableNewPage.pdf new file mode 100644 index 0000000..ec757cc Binary files /dev/null and b/src/test/reference/startTableNewPage.pdf differ