├── .gitignore ├── doc ├── example.png ├── example2.png ├── example3.png ├── example4.png ├── example_paragraph_cell.png └── example_overflow_on_same_page.png ├── src ├── test │ ├── resources │ │ ├── tux.png │ │ ├── check.png │ │ ├── glider.png │ │ ├── pic1.jpg │ │ ├── pic2.jpg │ │ ├── sample.png │ │ ├── OpenSansCondensed-Light.ttf │ │ └── logback.xml │ ├── reference │ │ ├── finalY.pdf │ │ ├── images.pdf │ │ ├── excelLike.pdf │ │ ├── cellSpanning.pdf │ │ ├── cellVerticalText.pdf │ │ ├── customCellDrawer.pdf │ │ ├── imageCellAlignment.pdf │ │ ├── overflowOnSamePage.pdf │ │ ├── regression │ │ │ ├── issue50.pdf │ │ │ ├── ringmanager.pdf │ │ │ ├── wordWrapping_issue20.pdf │ │ │ ├── wordWrapping_issue20_2.pdf │ │ │ ├── wordWrapping_issue20_3.pdf │ │ │ ├── repeatHeaderTable_issue111.pdf │ │ │ ├── repeatHeaderTable_issue111_2.pdf │ │ │ ├── repeatHeaderTable_issue111_3.pdf │ │ │ └── repeatHeaderTable_issue111_4.pdf │ │ ├── rowSpanningSizing.pdf │ │ ├── settingsOverriding.pdf │ │ ├── severalPagesTable1.pdf │ │ ├── severalPagesTable2.pdf │ │ ├── startTableNewPage.pdf │ │ ├── differentFontsInCells.pdf │ │ ├── fullTableExistingPage.pdf │ │ ├── settingsColumnSettings.pdf │ │ ├── startTableExistingPage.pdf │ │ ├── cellSpanningMultiplePages.pdf │ │ ├── customCellDrawerNoLombok.pdf │ │ ├── settingsOverridingBorders.pdf │ │ ├── severalPagesTableRepeatedHeader.pdf │ │ ├── imageCellSizingOnRowAndColSpanning.pdf │ │ ├── overflowOnSamePageRepeatableHeader.pdf │ │ └── severalPagesTableRepeatedHeaderMultipleRows.pdf │ └── java │ │ └── org │ │ └── vandeseer │ │ ├── easytable │ │ ├── structure │ │ │ ├── cell │ │ │ │ ├── ImageCellTest.java │ │ │ │ └── TextCellTest.java │ │ │ ├── RowTest.java │ │ │ └── TableTest.java │ │ └── util │ │ │ └── PdfUtilTest.java │ │ ├── MinimumWorkingExample.java │ │ ├── integrationtest │ │ ├── image │ │ │ ├── ImagesTest.java │ │ │ ├── ImageSizingOnRowSpanningTest.java │ │ │ └── ImageCellAlignmentTest.java │ │ ├── settings │ │ │ ├── SettingsColumnTest.java │ │ │ ├── SettingsTest.java │ │ │ ├── SettingsOverridingBordersTest.java │ │ │ └── SettingsOverridingTest.java │ │ ├── OverflowOnSamePageTableDrawerTest.java │ │ ├── custom │ │ │ ├── CustomCellDrawerTest.java │ │ │ └── CustomCellWithCustomDrawerUsingLombokTest.java │ │ ├── FinalYTest.java │ │ ├── OverflowOnSamePageRepeatableHeaderTableDrawerTest.java │ │ ├── StartPageTest.java │ │ └── RowSpanningSizingTest.java │ │ ├── performance │ │ └── PerformanceTest.java │ │ ├── TestUtils.java │ │ └── regressiontest │ │ ├── Issue28InfiniteLoopTest.java │ │ ├── Issue111RepeatHeaderTableTest.java │ │ ├── RingManagerTest.java │ │ └── Issue20WordWrappingTest.java └── main │ └── java │ └── org │ └── vandeseer │ └── easytable │ ├── settings │ ├── VerticalAlignment.java │ ├── HorizontalAlignment.java │ ├── BorderStyleInterface.java │ ├── BorderStyle.java │ └── Settings.java │ ├── structure │ ├── TableNotYetBuiltException.java │ ├── cell │ │ ├── TextCell.java │ │ ├── VerticalTextCell.java │ │ ├── ImageCell.java │ │ └── AbstractTextCell.java │ ├── Column.java │ └── Row.java │ ├── RowIsTooHighException.java │ ├── drawing │ ├── Drawer.java │ ├── PositionedRectangle.java │ ├── PositionedStyledText.java │ ├── DrawingContext.java │ ├── PositionedLine.java │ ├── cell │ │ ├── ImageCellDrawer.java │ │ ├── TextCellDrawer.java │ │ ├── VerticalTextCellDrawer.java │ │ └── AbstractCellDrawer.java │ └── DrawingUtil.java │ ├── util │ ├── FloatUtil.java │ └── PdfUtil.java │ ├── RepeatedHeaderTableDrawer.java │ ├── OverflowOnSamePageTableDrawer.java │ └── OverflowOnSamePageRepeatableHeaderTableDrawer.java ├── LICENSE ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | easytable.iml 4 | -------------------------------------------------------------------------------- /doc/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example.png -------------------------------------------------------------------------------- /doc/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example2.png -------------------------------------------------------------------------------- /doc/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example3.png -------------------------------------------------------------------------------- /doc/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example4.png -------------------------------------------------------------------------------- /src/test/resources/tux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/tux.png -------------------------------------------------------------------------------- /src/test/reference/finalY.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/finalY.pdf -------------------------------------------------------------------------------- /src/test/reference/images.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/images.pdf -------------------------------------------------------------------------------- /src/test/resources/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/check.png -------------------------------------------------------------------------------- /src/test/resources/glider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/glider.png -------------------------------------------------------------------------------- /src/test/resources/pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/pic1.jpg -------------------------------------------------------------------------------- /src/test/resources/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/pic2.jpg -------------------------------------------------------------------------------- /src/test/resources/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/sample.png -------------------------------------------------------------------------------- /doc/example_paragraph_cell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example_paragraph_cell.png -------------------------------------------------------------------------------- /src/test/reference/excelLike.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/excelLike.pdf -------------------------------------------------------------------------------- /src/test/reference/cellSpanning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/cellSpanning.pdf -------------------------------------------------------------------------------- /doc/example_overflow_on_same_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/doc/example_overflow_on_same_page.png -------------------------------------------------------------------------------- /src/test/reference/cellVerticalText.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/cellVerticalText.pdf -------------------------------------------------------------------------------- /src/test/reference/customCellDrawer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/customCellDrawer.pdf -------------------------------------------------------------------------------- /src/test/reference/imageCellAlignment.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/imageCellAlignment.pdf -------------------------------------------------------------------------------- /src/test/reference/overflowOnSamePage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/overflowOnSamePage.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/issue50.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/issue50.pdf -------------------------------------------------------------------------------- /src/test/reference/rowSpanningSizing.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/rowSpanningSizing.pdf -------------------------------------------------------------------------------- /src/test/reference/settingsOverriding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/settingsOverriding.pdf -------------------------------------------------------------------------------- /src/test/reference/severalPagesTable1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/severalPagesTable1.pdf -------------------------------------------------------------------------------- /src/test/reference/severalPagesTable2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/severalPagesTable2.pdf -------------------------------------------------------------------------------- /src/test/reference/startTableNewPage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/startTableNewPage.pdf -------------------------------------------------------------------------------- /src/test/reference/differentFontsInCells.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/differentFontsInCells.pdf -------------------------------------------------------------------------------- /src/test/reference/fullTableExistingPage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/fullTableExistingPage.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/ringmanager.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/ringmanager.pdf -------------------------------------------------------------------------------- /src/test/reference/settingsColumnSettings.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/settingsColumnSettings.pdf -------------------------------------------------------------------------------- /src/test/reference/startTableExistingPage.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/startTableExistingPage.pdf -------------------------------------------------------------------------------- /src/test/resources/OpenSansCondensed-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/resources/OpenSansCondensed-Light.ttf -------------------------------------------------------------------------------- /src/test/reference/cellSpanningMultiplePages.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/cellSpanningMultiplePages.pdf -------------------------------------------------------------------------------- /src/test/reference/customCellDrawerNoLombok.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/customCellDrawerNoLombok.pdf -------------------------------------------------------------------------------- /src/test/reference/settingsOverridingBorders.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/settingsOverridingBorders.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/wordWrapping_issue20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/wordWrapping_issue20.pdf -------------------------------------------------------------------------------- /src/test/reference/severalPagesTableRepeatedHeader.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/severalPagesTableRepeatedHeader.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/wordWrapping_issue20_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/wordWrapping_issue20_2.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/wordWrapping_issue20_3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/wordWrapping_issue20_3.pdf -------------------------------------------------------------------------------- /src/test/reference/imageCellSizingOnRowAndColSpanning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/imageCellSizingOnRowAndColSpanning.pdf -------------------------------------------------------------------------------- /src/test/reference/overflowOnSamePageRepeatableHeader.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/overflowOnSamePageRepeatableHeader.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/repeatHeaderTable_issue111.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/repeatHeaderTable_issue111.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/repeatHeaderTable_issue111_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/repeatHeaderTable_issue111_2.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/repeatHeaderTable_issue111_3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/repeatHeaderTable_issue111_3.pdf -------------------------------------------------------------------------------- /src/test/reference/regression/repeatHeaderTable_issue111_4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/regression/repeatHeaderTable_issue111_4.pdf -------------------------------------------------------------------------------- /src/test/reference/severalPagesTableRepeatedHeaderMultipleRows.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vandeseer/easytable/HEAD/src/test/reference/severalPagesTableRepeatedHeaderMultipleRows.pdf -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/settings/VerticalAlignment.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.settings; 2 | 3 | public enum VerticalAlignment { 4 | 5 | BOTTOM, MIDDLE, TOP 6 | 7 | } -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/TableNotYetBuiltException.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure; 2 | 3 | public class TableNotYetBuiltException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/settings/HorizontalAlignment.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.settings; 2 | 3 | public enum HorizontalAlignment { 4 | 5 | LEFT, CENTER, RIGHT, JUSTIFY 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/settings/BorderStyleInterface.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.settings; 2 | 3 | public interface BorderStyleInterface { 4 | 5 | float[] getPattern(); 6 | 7 | int getPhase(); 8 | 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/RowIsTooHighException.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable; 2 | 3 | public class RowIsTooHighException extends RuntimeException { 4 | 5 | public RowIsTooHighException(String message) { 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/Drawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | public interface Drawer { 4 | 5 | void drawContent(DrawingContext drawingContext); 6 | 7 | void drawBackground(DrawingContext drawingContext); 8 | 9 | void drawBorders(DrawingContext drawingContext); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/PositionedRectangle.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | import java.awt.*; 7 | 8 | @Builder(toBuilder = true) 9 | @Getter 10 | public class PositionedRectangle { 11 | 12 | private final float x; 13 | private final float y; 14 | private final float width; 15 | private final float height; 16 | private final Color color; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/PositionedStyledText.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import org.apache.pdfbox.pdmodel.font.PDFont; 6 | 7 | import java.awt.*; 8 | 9 | @Builder(toBuilder = true) 10 | @Getter 11 | public class PositionedStyledText { 12 | 13 | private final float x; 14 | private final float y; 15 | private final String text; 16 | private final PDFont font; 17 | private final int fontSize; 18 | private final Color color; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/DrawingContext.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.apache.pdfbox.pdmodel.PDPage; 6 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 7 | 8 | import java.awt.geom.Point2D; 9 | 10 | @Getter 11 | @AllArgsConstructor 12 | public class DrawingContext { 13 | 14 | private final PDPageContentStream contentStream; 15 | 16 | private final PDPage page; 17 | 18 | private final Point2D.Float startingPoint; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/cell/TextCell.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import lombok.Getter; 4 | import lombok.NonNull; 5 | import lombok.experimental.SuperBuilder; 6 | import org.vandeseer.easytable.drawing.Drawer; 7 | import org.vandeseer.easytable.drawing.cell.TextCellDrawer; 8 | 9 | @Getter 10 | @SuperBuilder(toBuilder = true) 11 | public class TextCell extends AbstractTextCell { 12 | 13 | @NonNull 14 | protected String text; 15 | 16 | protected Drawer createDefaultDrawer() { 17 | return new TextCellDrawer(this); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/PositionedLine.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import org.vandeseer.easytable.settings.BorderStyleInterface; 6 | 7 | import java.awt.*; 8 | 9 | @Builder(toBuilder = true) 10 | @Getter 11 | public class PositionedLine { 12 | 13 | private float width; 14 | private float startX; 15 | private float startY; 16 | private float endX; 17 | private float endY; 18 | private Color color; 19 | private Color resetColor; 20 | private BorderStyleInterface borderStyle; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/cell/VerticalTextCell.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import lombok.Getter; 4 | import lombok.NonNull; 5 | import lombok.experimental.SuperBuilder; 6 | import org.vandeseer.easytable.drawing.Drawer; 7 | import org.vandeseer.easytable.drawing.cell.VerticalTextCellDrawer; 8 | 9 | @Getter 10 | @SuperBuilder(toBuilder = true) 11 | public class VerticalTextCell extends AbstractTextCell { 12 | 13 | @NonNull 14 | private String text; 15 | 16 | protected Drawer createDefaultDrawer() { 17 | return new VerticalTextCellDrawer(this); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/settings/BorderStyle.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.settings; 2 | 3 | public enum BorderStyle implements BorderStyleInterface { 4 | 5 | SOLID(new float[]{}, 0), 6 | DOTTED(new float[]{1}, 1), 7 | DASHED(new float[]{5,2}, 1); 8 | 9 | private final float[] pattern; 10 | private final int phase; 11 | 12 | BorderStyle(float[] pattern, int phase) { 13 | this.pattern = pattern; 14 | this.phase = phase; 15 | } 16 | 17 | public float[] getPattern() { 18 | return pattern; 19 | } 20 | 21 | public int getPhase() { 22 | return phase; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/util/FloatUtil.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.util; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public final class FloatUtil { 8 | 9 | /** 10 | * The delta that is still acceptable in float comparisons. 11 | */ 12 | public static final double EPSILON = 0.0001; 13 | 14 | 15 | /** 16 | * Floats can't hold any value (see https://www.geeksforgeeks.org/rounding-off-errors-java/). 17 | * So this method is a workaround to compare to floats - all values where the difference is lower than {@value EPSILON} are equal. 18 | * 19 | * @param x left value 20 | * @param y right value 21 | * @return true, if difference between x and y lower than {@value EPSILON}. 22 | */ 23 | public static boolean isEqualInEpsilon(float x, float y) { 24 | float difference = Math.abs(y - x); 25 | return difference < EPSILON; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/easytable/structure/cell/ImageCellTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import org.apache.pdfbox.io.IOUtils; 4 | import org.apache.pdfbox.pdmodel.PDDocument; 5 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import org.junit.rules.ExpectedException; 9 | import org.vandeseer.easytable.structure.TableNotYetBuiltException; 10 | 11 | import java.io.IOException; 12 | 13 | public class ImageCellTest { 14 | 15 | @Rule 16 | public final ExpectedException exception = ExpectedException.none(); 17 | 18 | @Test 19 | public void getHeight_shouldThrowExceptionIfTableNotYetBuilt() throws IOException { 20 | final byte[] bytes = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream("pic1.jpg")); 21 | final PDImageXObject image = PDImageXObject.createFromByteArray(new PDDocument(), bytes, "test1"); 22 | 23 | AbstractCell cell = ImageCell.builder().image(image).build(); 24 | 25 | exception.expect(TableNotYetBuiltException.class); 26 | cell.getHeight(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/cell/ImageCellDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing.cell; 2 | 3 | import lombok.NoArgsConstructor; 4 | import lombok.SneakyThrows; 5 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 6 | import org.vandeseer.easytable.drawing.DrawingContext; 7 | import org.vandeseer.easytable.settings.HorizontalAlignment; 8 | import org.vandeseer.easytable.structure.cell.ImageCell; 9 | 10 | import java.awt.geom.Point2D; 11 | 12 | @NoArgsConstructor 13 | public class ImageCellDrawer extends AbstractCellDrawer { 14 | 15 | public ImageCellDrawer(ImageCell cell) { 16 | this.cell = cell; 17 | } 18 | 19 | @Override 20 | @SneakyThrows 21 | public void drawContent(DrawingContext drawingContext) { 22 | final PDPageContentStream contentStream = drawingContext.getContentStream(); 23 | final float moveX = drawingContext.getStartingPoint().x; 24 | 25 | final Point2D.Float size = cell.getFitSize(); 26 | final Point2D.Float drawAt = new Point2D.Float(); 27 | 28 | // Handle horizontal alignment by adjusting the xOffset 29 | float xOffset = moveX + cell.getPaddingLeft(); 30 | if (cell.getSettings().getHorizontalAlignment() == HorizontalAlignment.RIGHT) { 31 | xOffset = moveX + (cell.getWidth() - (size.x + cell.getPaddingRight())); 32 | 33 | } else if (cell.getSettings().getHorizontalAlignment() == HorizontalAlignment.CENTER) { 34 | final float diff = (cell.getWidth() - size.x) / 2; 35 | xOffset = moveX + diff; 36 | 37 | } 38 | 39 | drawAt.x = xOffset; 40 | drawAt.y = drawingContext.getStartingPoint().y + getAdaptionForVerticalAlignment() - size.y; 41 | 42 | contentStream.drawImage(cell.getImage(), drawAt.x, drawAt.y, size.x, size.y); 43 | } 44 | 45 | @Override 46 | protected float calculateInnerHeight() { 47 | return (float) cell.getFitSize().getY(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/cell/ImageCell.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NonNull; 6 | import lombok.experimental.SuperBuilder; 7 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 8 | import org.vandeseer.easytable.drawing.Drawer; 9 | import org.vandeseer.easytable.drawing.cell.ImageCellDrawer; 10 | 11 | import java.awt.geom.Point2D; 12 | 13 | @Getter 14 | @SuperBuilder(toBuilder = true) 15 | public class ImageCell extends AbstractCell { 16 | 17 | @Builder.Default 18 | private final float scale = 1.0f; 19 | 20 | @NonNull 21 | private PDImageXObject image; 22 | 23 | private float maxHeight; 24 | 25 | @Override 26 | public float getMinHeight() { 27 | return (getFitSize().y + getVerticalPadding()) > super.getMinHeight() 28 | ? (getFitSize().y + getVerticalPadding()) 29 | : super.getMinHeight(); 30 | } 31 | 32 | @Override 33 | protected Drawer createDefaultDrawer() { 34 | return new ImageCellDrawer(this); 35 | } 36 | 37 | public Point2D.Float getFitSize() { 38 | final Point2D.Float sizes = new Point2D.Float(); 39 | float scaledWidth = image.getWidth() * getScale(); 40 | float scaledHeight = image.getHeight() * getScale(); 41 | final float resultingWidth = getWidth() - getHorizontalPadding(); 42 | 43 | // maybe reduce the image to fit in column 44 | if (scaledWidth > resultingWidth) { 45 | scaledHeight = (resultingWidth / scaledWidth) * scaledHeight; 46 | scaledWidth = resultingWidth; 47 | } 48 | 49 | if (maxHeight > 0.0f && scaledHeight > maxHeight) { 50 | scaledWidth = (maxHeight / scaledHeight) * scaledWidth; 51 | scaledHeight = maxHeight; 52 | } 53 | 54 | sizes.x = scaledWidth; 55 | sizes.y = scaledHeight; 56 | 57 | return sizes; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/DrawingUtil.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing; 2 | 3 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 4 | 5 | import java.awt.*; 6 | import java.io.IOException; 7 | 8 | import static org.vandeseer.easytable.settings.BorderStyle.SOLID; 9 | 10 | public class DrawingUtil { 11 | 12 | private DrawingUtil() { 13 | } 14 | 15 | public static void drawText(PDPageContentStream contentStream, PositionedStyledText styledText) throws IOException { 16 | contentStream.beginText(); 17 | contentStream.setNonStrokingColor(styledText.getColor()); 18 | contentStream.setFont(styledText.getFont(), styledText.getFontSize()); 19 | contentStream.newLineAtOffset(styledText.getX(), styledText.getY()); 20 | contentStream.showText(styledText.getText()); 21 | contentStream.endText(); 22 | contentStream.setCharacterSpacing(0); 23 | } 24 | 25 | public static void drawLine(PDPageContentStream contentStream, PositionedLine line) throws IOException { 26 | contentStream.moveTo(line.getStartX(), line.getStartY()); 27 | contentStream.setLineWidth(line.getWidth()); 28 | contentStream.lineTo(line.getEndX(), line.getEndY()); 29 | contentStream.setStrokingColor(line.getColor()); 30 | contentStream.setLineDashPattern(line.getBorderStyle().getPattern(), line.getBorderStyle().getPhase()); 31 | contentStream.stroke(); 32 | contentStream.setStrokingColor(line.getResetColor()); 33 | contentStream.setLineDashPattern(SOLID.getPattern(), SOLID.getPhase()); 34 | } 35 | 36 | public static void drawRectangle(PDPageContentStream contentStream, PositionedRectangle rectangle) 37 | throws IOException { 38 | contentStream.setNonStrokingColor(rectangle.getColor()); 39 | 40 | contentStream.addRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight()); 41 | contentStream.fill(); 42 | 43 | // Reset NonStrokingColor to default value 44 | contentStream.setNonStrokingColor(Color.BLACK); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/easytable/structure/RowTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure; 2 | 3 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import org.vandeseer.easytable.structure.cell.TextCell; 8 | 9 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.COURIER_BOLD; 10 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.greaterThan; 13 | import static org.vandeseer.easytable.settings.HorizontalAlignment.CENTER; 14 | 15 | public class RowTest { 16 | 17 | @Rule 18 | public final ExpectedException exception = ExpectedException.none(); 19 | 20 | @Test 21 | public void getHeightShouldThrowExceptionIfNotYetRendered() { 22 | final Row row = Row.builder() 23 | .add(TextCell.builder().text("This text should break because too long").colSpan(2).borderWidth(1).build()) 24 | .add(TextCell.builder().text("Booz").build()) 25 | .wordBreak(true) 26 | .font(new PDType1Font(COURIER_BOLD)).fontSize(8) 27 | .build(); 28 | 29 | exception.expect(TableNotYetBuiltException.class); 30 | row.getHeight(); 31 | } 32 | 33 | @Test 34 | public void getHeightShouldReturnValueIfTableIsBuilt() { 35 | final Table.TableBuilder tableBuilder = Table.builder() 36 | .addColumnsOfWidth(10, 10, 10) 37 | .horizontalAlignment(CENTER) 38 | .fontSize(10).font(new PDType1Font(HELVETICA)) 39 | .wordBreak(false); 40 | 41 | final Row row = Row.builder() 42 | .add(TextCell.builder().text("iVgebALheQlBkxtDyNDrhKv").colSpan(2).borderWidth(1).build()) 43 | .add(TextCell.builder().text("Booz").build()) 44 | .font(new PDType1Font(COURIER_BOLD)).fontSize(8) 45 | .build(); 46 | 47 | tableBuilder.addRow(row); 48 | tableBuilder.build(); 49 | 50 | assertThat(row.getHeight(), greaterThan(0f)); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/RepeatedHeaderTableDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable; 2 | 3 | import lombok.Builder; 4 | import lombok.experimental.SuperBuilder; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.vandeseer.easytable.structure.Row; 8 | 9 | import java.awt.geom.Point2D; 10 | import java.io.IOException; 11 | import java.util.function.Supplier; 12 | 13 | @SuperBuilder 14 | public class RepeatedHeaderTableDrawer extends TableDrawer { 15 | 16 | @Builder.Default 17 | private int numberOfRowsToRepeat = 1; 18 | 19 | private Float headerHeight; 20 | 21 | @Override 22 | protected void drawPage(PageData pageData) { 23 | if (pageData.firstRowOnPage != 0) { 24 | float adaption = 0; 25 | for (int i = 0; i < numberOfRowsToRepeat; i++) { 26 | adaption += table.getRows().get(i).getHeight(); 27 | Point2D.Float startPoint = new Point2D.Float(this.startX, 28 | this.startY + calculateHeightForFirstRows() - adaption); 29 | drawRow(startPoint, table.getRows().get(i), i, (drawer, drawingContext) -> { 30 | drawer.drawBackground(drawingContext); 31 | drawer.drawContent(drawingContext); 32 | drawer.drawBorders(drawingContext); 33 | }); 34 | } 35 | } 36 | 37 | drawerList.forEach(drawer -> drawWithFunction(pageData, new Point2D.Float(this.startX, this.startY), drawer)); 38 | } 39 | 40 | @Override 41 | protected void determinePageToStartTable(float yOffsetOnNewPage) { 42 | float minimumRowsToFitHeight = 0; 43 | int minimumRowsToFit = table.getRows().size() > numberOfRowsToRepeat 44 | ? numberOfRowsToRepeat + 1 45 | : numberOfRowsToRepeat; 46 | 47 | for (final Row row : table.getRows().subList(0, minimumRowsToFit)) 48 | minimumRowsToFitHeight += row.getHeight(); 49 | 50 | if (startY - minimumRowsToFitHeight < endY) { 51 | startY = yOffsetOnNewPage + calculateHeightForFirstRows(); 52 | startTableInNewPage = true; 53 | } 54 | } 55 | 56 | @Override 57 | public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) 58 | throws IOException { 59 | super.draw(documentSupplier, pageSupplier, yOffset + calculateHeightForFirstRows()); 60 | } 61 | 62 | private float calculateHeightForFirstRows() { 63 | if (headerHeight != null) { 64 | return headerHeight; 65 | } 66 | 67 | float height = 0; 68 | for (int i = 0; i < numberOfRowsToRepeat; i++) { 69 | height += table.getRows().get(i).getHeight(); 70 | } 71 | 72 | // Cache and return 73 | headerHeight = height; 74 | return height; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/MinimumWorkingExample.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 6 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 7 | import org.vandeseer.easytable.TableDrawer; 8 | import org.vandeseer.easytable.settings.BorderStyle; 9 | import org.vandeseer.easytable.settings.HorizontalAlignment; 10 | import org.vandeseer.easytable.structure.Row; 11 | import org.vandeseer.easytable.structure.Table; 12 | import org.vandeseer.easytable.structure.cell.TextCell; 13 | 14 | import java.awt.*; 15 | import java.io.IOException; 16 | 17 | public class MinimumWorkingExample { 18 | 19 | public static void main(String[] args) throws IOException { 20 | 21 | try (PDDocument document = new PDDocument()) { 22 | final PDPage page = new PDPage(PDRectangle.A4); 23 | document.addPage(page); 24 | 25 | try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { 26 | 27 | // Build the table 28 | Table myTable = Table.builder() 29 | .addColumnsOfWidth(200, 200) 30 | .padding(2) 31 | .addRow(Row.builder() 32 | .add(TextCell.builder().text("One One").borderWidth(4).borderColorLeft(Color.MAGENTA).backgroundColor(Color.WHITE).build()) 33 | .add(TextCell.builder().text("One Two").borderWidth(0).backgroundColor(Color.YELLOW).build()) 34 | .build()) 35 | .addRow(Row.builder() 36 | .padding(10) 37 | .add(TextCell.builder().text("Two One").textColor(Color.RED).build()) 38 | .add(TextCell.builder().text("Two Two") 39 | .borderWidthRight(1f) 40 | .borderStyleRight(BorderStyle.DOTTED) 41 | .horizontalAlignment(HorizontalAlignment.RIGHT) 42 | .build()) 43 | .build()) 44 | .build(); 45 | 46 | // Set up the drawer 47 | TableDrawer tableDrawer = TableDrawer.builder() 48 | .contentStream(contentStream) 49 | .startX(20f) 50 | .startY(page.getMediaBox().getUpperRightY() - 20f) 51 | .table(myTable) 52 | .build(); 53 | 54 | // And go for it! 55 | tableDrawer.draw(); 56 | } 57 | 58 | document.save("example.pdf"); 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/image/ImagesTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.image; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.io.IOUtils; 6 | import org.apache.pdfbox.pdmodel.PDDocument; 7 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 8 | import org.junit.Test; 9 | import org.vandeseer.TestUtils; 10 | import org.vandeseer.easytable.structure.Row; 11 | import org.vandeseer.easytable.structure.Table; 12 | import org.vandeseer.easytable.structure.cell.ImageCell; 13 | import org.vandeseer.easytable.structure.cell.TextCell; 14 | 15 | import static junit.framework.TestCase.assertTrue; 16 | import static org.vandeseer.TestUtils.getActualPdfFor; 17 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 18 | import static org.vandeseer.easytable.settings.HorizontalAlignment.RIGHT; 19 | 20 | public class ImagesTest { 21 | 22 | public static final String FILE_NAME = "images.pdf"; 23 | 24 | @Test 25 | public void testImage() throws Exception { 26 | 27 | final Table.TableBuilder tableBuilder = Table.builder() 28 | .addColumnsOfWidth(200, 200); 29 | 30 | final byte[] bytes1 = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream("pic1.jpg")); 31 | final PDImageXObject image1 = PDImageXObject.createFromByteArray(new PDDocument(), bytes1, "test1"); 32 | 33 | final byte[] bytes2 = IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream("pic2.jpg")); 34 | final PDImageXObject image2 = PDImageXObject.createFromByteArray(new PDDocument(), bytes2, "test2"); 35 | 36 | tableBuilder.addRow( 37 | Row.builder() 38 | .add(TextCell.builder().text("first").build()) 39 | .add(TextCell.builder().text("second").horizontalAlignment(RIGHT).build()) 40 | .build()); 41 | 42 | 43 | tableBuilder.addRow( 44 | Row.builder() 45 | .add(ImageCell.builder().image(image1).scale(0.13f).borderWidth(1).build()) 46 | .add(ImageCell.builder().image(image2).borderWidth(1).build()) 47 | .build()); 48 | 49 | tableBuilder.addRow( 50 | Row.builder() 51 | .add(TextCell.builder() 52 | .text("images from \"https://www.techrepublic.com/pictures/the-21-best-it-and-tech-memes-on-the-internet/5/\"") 53 | .colSpan(2) 54 | .fontSize(6) 55 | .borderWidth(1) 56 | .build()) 57 | .build() 58 | ); 59 | 60 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME, tableBuilder.build()); 61 | 62 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 63 | assertTrue(compareResult.isEqual()); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/settings/SettingsColumnTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.settings; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 6 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 7 | import org.junit.Test; 8 | import org.vandeseer.TestUtils; 9 | import org.vandeseer.easytable.structure.Column; 10 | import org.vandeseer.easytable.structure.Row; 11 | import org.vandeseer.easytable.structure.Table; 12 | import org.vandeseer.easytable.structure.cell.TextCell; 13 | 14 | import java.awt.*; 15 | import java.io.IOException; 16 | 17 | import static junit.framework.TestCase.assertTrue; 18 | import static org.vandeseer.TestUtils.getActualPdfFor; 19 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 20 | 21 | public class SettingsColumnTest { 22 | 23 | private final static Color PURPLE_LIGHT_1 = new Color(206, 186, 230); 24 | private final static Color PURPLE_LIGHT_2 = new Color(230, 218, 242); 25 | 26 | private static final String FILE_NAME = "settingsColumnSettings.pdf"; 27 | 28 | @Test 29 | public void differentSettings() throws IOException { 30 | final Table.TableBuilder tableBuilder = Table.builder() 31 | .addColumn( 32 | Column.builder() 33 | .width(100) 34 | .borderWidthLeft(2) 35 | .borderColorLeft(Color.BLUE) 36 | .backgroundColor(PURPLE_LIGHT_1) 37 | .build() 38 | ) 39 | .addColumn(Column.builder().width(100).fontSize(13).build()) 40 | .addColumn(Column.builder().width(100).backgroundColor(PURPLE_LIGHT_2).build()) 41 | .fontSize(8) 42 | .font(new PDType1Font(Standard14Fonts.FontName.HELVETICA)) 43 | .wordBreak(true); 44 | 45 | for (int i = 0; i < 10; i++) { 46 | tableBuilder.addRow( 47 | Row.builder() 48 | .add(TextCell.builder().text("abcd").build()) 49 | .add(TextCell.builder().text("abcd").build()) 50 | .add(TextCell.builder().text("abcd").build()) 51 | .build() 52 | ); 53 | } 54 | 55 | tableBuilder.addRow( 56 | Row.builder() 57 | .add(TextCell.builder().text("abcd").build()) 58 | .add(TextCell.builder().text("abcd").build()) 59 | .add(TextCell.builder().text("abcd").build()) 60 | .backgroundColor(Color.GREEN) 61 | .build() 62 | ); 63 | 64 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME, tableBuilder.build()); 65 | 66 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 67 | assertTrue(compareResult.isEqual()); 68 | } 69 | 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/OverflowOnSamePageTableDrawerTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 8 | import org.junit.Test; 9 | import org.vandeseer.TestUtils; 10 | import org.vandeseer.easytable.OverflowOnSamePageTableDrawer; 11 | import org.vandeseer.easytable.structure.Row; 12 | import org.vandeseer.easytable.structure.Table; 13 | import org.vandeseer.easytable.structure.cell.TextCell; 14 | 15 | import java.awt.*; 16 | import java.io.IOException; 17 | 18 | import static junit.framework.TestCase.assertTrue; 19 | import static org.vandeseer.TestUtils.getActualPdfFor; 20 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 21 | 22 | public class OverflowOnSamePageTableDrawerTest { 23 | 24 | private static final String OVERFLOW_ON_SAME_PAGE_PDF = "overflowOnSamePage.pdf"; 25 | 26 | @Test 27 | public void createTableWithOverflowOnSamePage() throws IOException { 28 | 29 | try (final PDDocument document = new PDDocument()) { 30 | 31 | OverflowOnSamePageTableDrawer.builder() 32 | .table(createTable()) 33 | .startX(50) 34 | .lanesPerPage(3) 35 | .spaceInBetween(25) 36 | .endY(50F) // note: if not set, table is drawn over the end of the page 37 | .build() 38 | .draw(() -> document, () -> new PDPage(PDRectangle.A4), 50f); 39 | 40 | document.save(TestUtils.TARGET_FOLDER + "/" + OVERFLOW_ON_SAME_PAGE_PDF); 41 | } 42 | 43 | final CompareResult compareResult = new PdfComparator<>( 44 | getExpectedPdfFor(OVERFLOW_ON_SAME_PAGE_PDF), 45 | getActualPdfFor(OVERFLOW_ON_SAME_PAGE_PDF) 46 | ).compare(); 47 | 48 | assertTrue(compareResult.isEqual()); 49 | } 50 | 51 | private Table createTable() { 52 | final Table.TableBuilder tableBuilder = Table.builder() 53 | .addColumnOfWidth(60) 54 | .addColumnOfWidth(60); 55 | 56 | TextCell dummyHeaderCell = TextCell.builder() 57 | .text("HD") 58 | .backgroundColor(Color.BLUE) 59 | .textColor(Color.WHITE) 60 | .borderWidth(1F) 61 | .build(); 62 | 63 | tableBuilder.addRow( 64 | Row.builder() 65 | .add(dummyHeaderCell) 66 | .add(dummyHeaderCell) 67 | .build()); 68 | 69 | for (int i = 0; i < 200; i++) { 70 | tableBuilder.addRow( 71 | Row.builder() 72 | .add(TextCell.builder() 73 | .text("a " + i) 74 | .borderWidth(1F) 75 | .build()) 76 | .add(TextCell.builder() 77 | .text("b " + i) 78 | .borderWidth(1F) 79 | .build()) 80 | .build()); 81 | } 82 | 83 | return tableBuilder.build(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/performance/PerformanceTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.performance; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 6 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 7 | import org.vandeseer.TestUtils; 8 | import org.vandeseer.easytable.TableDrawer; 9 | import org.vandeseer.easytable.structure.Row; 10 | import org.vandeseer.easytable.structure.Table; 11 | import org.vandeseer.easytable.structure.cell.TextCell; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | 16 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 17 | 18 | public class PerformanceTest { 19 | 20 | private static final int LINES_MAX = 1000; 21 | private static final int LINES_STEP = 100; 22 | 23 | private static final int CHARS_PER_CELL_MAX = 300; 24 | private static final int CHARS_PER_CELL_STEP = 50; 25 | 26 | 27 | private static final PDRectangle PAGE = PDRectangle.A4; 28 | private final static int MARGIN = 50; 29 | private final static float START_Y = PAGE.getHeight() - MARGIN; 30 | private final static float WIDTH = PAGE.getWidth() - 2 * MARGIN; 31 | 32 | private static final String TARGET_FOLDER_PERFORMANCE = TestUtils.TARGET_FOLDER + "/performance"; 33 | 34 | 35 | public static void main(String[] args) throws IOException { 36 | new File(TARGET_FOLDER_PERFORMANCE).mkdirs(); 37 | 38 | final PerformanceTest performanceTest = new PerformanceTest(); 39 | 40 | for (int numLines = LINES_STEP; numLines <= LINES_MAX; numLines += LINES_STEP) { 41 | for (int charsPerCell = 0; charsPerCell <= CHARS_PER_CELL_MAX; charsPerCell += CHARS_PER_CELL_STEP) { 42 | performanceTest.create(numLines, charsPerCell); 43 | } 44 | } 45 | } 46 | 47 | private void create(int numLines, int numCharsPerCell) throws IOException { 48 | final long start = System.currentTimeMillis(); 49 | final int numberOfPages; 50 | 51 | try (PDDocument document = new PDDocument()) { 52 | Table.TableBuilder tableBuilder = Table.builder() 53 | .addColumnsOfWidth(WIDTH) 54 | .font(new PDType1Font(HELVETICA)) 55 | .fontSize(10); 56 | 57 | for (int i = 0; i < numLines; i++) { 58 | tableBuilder.addRow( 59 | Row.builder().add( 60 | TextCell.builder() 61 | .text(repeat('x', numCharsPerCell)) 62 | .borderWidth(1) 63 | .build()) 64 | .build()); 65 | } 66 | 67 | TableDrawer.builder() 68 | .table(tableBuilder.build()) 69 | .startX(MARGIN) 70 | .startY(START_Y) 71 | .endY(MARGIN) 72 | .build() 73 | .draw(() -> document, () -> new PDPage(PAGE), MARGIN); 74 | 75 | numberOfPages = document.getNumberOfPages(); 76 | document.save(String.format(TARGET_FOLDER_PERFORMANCE + "/performance_%d_%d.pdf", numLines, numCharsPerCell)); 77 | } 78 | 79 | long duration = System.currentTimeMillis() - start; 80 | System.out.println(String.format("Generating %d lines with %d characters each results in %d pages and took %d ms", numLines, numCharsPerCell, numberOfPages, duration)); 81 | } 82 | 83 | private static String repeat(char ch, int repeat) { 84 | if (repeat <= 0) { 85 | return ""; 86 | } else { 87 | char[] buf = new char[repeat]; 88 | 89 | for(int i = repeat - 1; i >= 0; --i) { 90 | buf[i] = ch; 91 | } 92 | 93 | return new String(buf); 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/custom/CustomCellDrawerTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.custom; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 8 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 9 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 10 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 11 | import org.junit.Test; 12 | import org.vandeseer.TestUtils; 13 | import org.vandeseer.easytable.TableDrawer; 14 | import org.vandeseer.easytable.drawing.DrawingContext; 15 | import org.vandeseer.easytable.drawing.PositionedStyledText; 16 | import org.vandeseer.easytable.drawing.cell.TextCellDrawer; 17 | import org.vandeseer.easytable.structure.Row; 18 | import org.vandeseer.easytable.structure.Table; 19 | import org.vandeseer.easytable.structure.cell.TextCell; 20 | 21 | import java.io.IOException; 22 | 23 | import static junit.framework.TestCase.assertTrue; 24 | import static org.vandeseer.TestUtils.getActualPdfFor; 25 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 26 | 27 | public class CustomCellDrawerTest { 28 | 29 | public static final String FILE_NAME = "customCellDrawerNoLombok.pdf"; 30 | 31 | private static final TextCellDrawer CUSTOM_DRAWER = new TextCellDrawer() { 32 | @Override 33 | protected void drawText(DrawingContext drawingContext, PositionedStyledText text) throws IOException { 34 | System.out.println("My custom drawer is called :-)"); 35 | super.drawText(drawingContext, text.toBuilder().text(text.getText().toUpperCase()).build()); 36 | } 37 | }; 38 | 39 | @Test 40 | public void testCustomCellDrawer() throws IOException { 41 | 42 | try (final PDDocument document = new PDDocument()) { 43 | 44 | final PDPage page = new PDPage(PDRectangle.A4); 45 | document.addPage(page); 46 | 47 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, page)) { 48 | 49 | TableDrawer.builder() 50 | .contentStream(contentStream) 51 | .table(createSimpleTable()) 52 | .startX(50) 53 | .startY(page.getMediaBox().getHeight() - 50) 54 | .build() 55 | .draw(); 56 | 57 | } 58 | 59 | document.save(TestUtils.TARGET_FOLDER + "/" + FILE_NAME); 60 | } 61 | 62 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 63 | assertTrue(compareResult.isEqual()); 64 | } 65 | 66 | private static Table createSimpleTable() { 67 | return Table.builder() 68 | .addColumnsOfWidth(100, 100, 100, 100) 69 | .fontSize(8) 70 | .font(new PDType1Font(Standard14Fonts.FontName.HELVETICA)) 71 | .addRow(Row.builder() 72 | .add(TextCell.builder().drawer(CUSTOM_DRAWER).borderWidth(1).text("One").build()) 73 | .add(TextCell.builder().borderWidth(1).text("Two").build()) 74 | .add(TextCell.builder().borderWidth(1).text("Three").build()) 75 | .add(TextCell.builder().borderWidth(1).text("Four").build()) 76 | .build()) 77 | .build(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/OverflowOnSamePageTableDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | import org.apache.pdfbox.pdmodel.PDDocument; 9 | import org.apache.pdfbox.pdmodel.PDPage; 10 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 11 | 12 | import java.awt.geom.Point2D; 13 | import java.io.IOException; 14 | import java.util.Queue; 15 | import java.util.function.Supplier; 16 | 17 | import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; 18 | 19 | /** 20 | * Experimental drawer that allows for several "lanes" on the same 21 | * page in case it would overflow the current page. 22 | */ 23 | @SuperBuilder(toBuilder = true) 24 | public class OverflowOnSamePageTableDrawer extends TableDrawer { 25 | 26 | @Builder.Default 27 | private final int lanesPerPage = 2; 28 | 29 | @Builder.Default 30 | private final float spaceInBetween = 10f; 31 | 32 | // This is really meant as a private field. 33 | // Unfortunately it might be settable by the builder and we can't make it final :/ 34 | @Getter(AccessLevel.NONE) 35 | @Setter(AccessLevel.NONE) 36 | @Builder.Default 37 | private int actualTableLane = 1; 38 | 39 | protected void drawPage(PageData pageData) { 40 | drawerList.forEach(drawer -> 41 | drawWithFunction(pageData, new Point2D.Float(this.startX + calculateXOffset(), this.startY), drawer) 42 | ); 43 | } 44 | 45 | private float calculateXOffset() { 46 | final float widthOfTableLanes = (actualTableLane - 1) * table.getWidth(); 47 | final float spacing = 48 | actualTableLane > 1 49 | ? (actualTableLane - 1) * spaceInBetween 50 | : 0; 51 | 52 | return widthOfTableLanes + spacing; 53 | } 54 | 55 | public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) throws IOException { 56 | final PDDocument document = documentSupplier.get(); 57 | 58 | // We create one throwaway page to be able to calculate the page data upfront 59 | float startOnNewPage = pageSupplier.get().getMediaBox().getHeight() - yOffset; 60 | determinePageToStartTable(startOnNewPage); 61 | final Queue pageDataQueue = computeRowsOnPagesWithNewPageStartOf(startOnNewPage); 62 | 63 | for (int i = 0; !pageDataQueue.isEmpty(); i++) { 64 | final PDPage pageToDrawOn = determinePageToDraw(i, document, pageSupplier); 65 | 66 | if ((i == 0 && startTableInNewPage) || i > 0 || document.getNumberOfPages() == 0 || actualTableLane != lanesPerPage) { 67 | startTableInNewPage = false; 68 | } 69 | 70 | if (i == 0) { 71 | tableStartPage = pageToDrawOn; 72 | } 73 | 74 | try (final PDPageContentStream newPageContentStream = new PDPageContentStream(document, pageToDrawOn, APPEND, compress)) { 75 | for (int j = 1; j <= lanesPerPage && !pageDataQueue.isEmpty(); j++) { 76 | actualTableLane = j; 77 | if (actualTableLane > 1) { 78 | this.startY = startOnNewPage; 79 | } 80 | this.contentStream(newPageContentStream) 81 | .page(pageToDrawOn) 82 | .drawPage(pageDataQueue.poll()); 83 | } 84 | } 85 | 86 | startY(pageToDrawOn.getMediaBox().getHeight() - yOffset); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/easytable/structure/TableTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure; 2 | 3 | import org.junit.Test; 4 | import org.vandeseer.easytable.structure.Table.TableBuilder; 5 | import org.vandeseer.easytable.structure.cell.AbstractCell; 6 | import org.vandeseer.easytable.structure.cell.TextCell; 7 | import org.vandeseer.easytable.util.PdfUtil; 8 | 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.hamcrest.Matchers.equalTo; 11 | import static org.hamcrest.Matchers.is; 12 | 13 | public class TableTest { 14 | 15 | @Test 16 | public void getNumberOfColumns_tableBuilderWithThreeColumns() { 17 | final TableBuilder tableBuilder = Table.builder() 18 | .addColumnOfWidth(12) 19 | .addColumnOfWidth(34) 20 | .addColumnOfWidth(56); 21 | final Table table = tableBuilder.build(); 22 | 23 | assertThat(table.getNumberOfColumns(), equalTo(3)); 24 | } 25 | 26 | @Test 27 | public void getWidth_tableBuilderWithTwoColumns() { 28 | final TableBuilder tableBuilder = Table.builder() 29 | .addColumnOfWidth(20) 30 | .addColumnOfWidth(40); 31 | final Table table = tableBuilder.build(); 32 | 33 | assertThat(table.getWidth(), equalTo(60f)); 34 | } 35 | 36 | @Test 37 | public void getRows_tableBuilderWithOneRow() { 38 | final TableBuilder tableBuilder = Table.builder(); 39 | tableBuilder.addColumnOfWidth(12) 40 | .addColumnOfWidth(34); 41 | final Row row = Row.builder() 42 | .add(TextCell.builder().text("11").build()) 43 | .add(TextCell.builder().text("12").build()) 44 | .build(); 45 | tableBuilder.addRow(row); 46 | final Table table = tableBuilder.build(); 47 | 48 | assertThat(table.getRows().size(), equalTo(1)); 49 | } 50 | 51 | @Test 52 | public void getHeight_twoRowsWithDifferentPaddings() { 53 | final Table table = Table.builder() 54 | .addColumnOfWidth(12) 55 | .addColumnOfWidth(34) 56 | .fontSize(12) 57 | .addRow(Row.builder() 58 | .add(TextCell.builder().text("11").paddingTop(35).paddingBottom(15).build()) 59 | .add(TextCell.builder().text("12").paddingTop(15).paddingBottom(25).build()) 60 | .build()) 61 | .build(); 62 | 63 | // highest cell (50) + actual font height 64 | final float actualFontHeight = PdfUtil.getFontHeight(table.getSettings().getFont(), 12); 65 | assertThat(table.getHeight(), equalTo(50 + actualFontHeight)); 66 | } 67 | 68 | @Test 69 | public void tableBuilder_shouldConnectStructureCorrectly() { 70 | // We are spanning two columns in the first cell 71 | AbstractCell lastCell = TextCell.builder().text("").build(); 72 | 73 | Table table = Table.builder() 74 | .addColumnOfWidth(10) 75 | .addColumnOfWidth(20) 76 | .addColumnOfWidth(30) 77 | .addRow(Row.builder() 78 | .add(TextCell.builder().text("").colSpan(2).build()) 79 | .add(lastCell) 80 | .build()) 81 | .build(); 82 | 83 | Column lastColumn = table.getColumns().get(table.getColumns().size() - 1); 84 | 85 | // So make sure the second cell is connected correctly to the last column 86 | assertThat(lastCell.getColumn(), is(lastColumn)); 87 | } 88 | 89 | // TODO check addRow ... also check the setup of the structure with row spanning active 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/image/ImageSizingOnRowSpanningTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.image; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 6 | import org.junit.Test; 7 | import org.vandeseer.TestUtils; 8 | import org.vandeseer.easytable.structure.Row; 9 | import org.vandeseer.easytable.structure.Table; 10 | import org.vandeseer.easytable.structure.cell.ImageCell; 11 | import org.vandeseer.easytable.structure.cell.TextCell; 12 | 13 | import static junit.framework.TestCase.assertTrue; 14 | import static org.vandeseer.TestUtils.getActualPdfFor; 15 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 16 | 17 | // Test for ISSUE-40 https://github.com/vandeseer/easytable/issues/40 18 | // and ISSUE-60 https://github.com/vandeseer/easytable/issues/64 19 | public class ImageSizingOnRowSpanningTest { 20 | 21 | public static final String FILE_NAME = "imageCellSizingOnRowAndColSpanning.pdf"; 22 | 23 | @Test 24 | public void testImageSizingOnRowAndColSpanning() throws Exception { 25 | final PDImageXObject glider = TestUtils.createGliderImage(); 26 | 27 | TestUtils.createAndSaveDocumentWithTables( 28 | FILE_NAME, 29 | createRowSpanningTable(glider), 30 | createColSpanningTable(glider) 31 | ); 32 | 33 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 34 | assertTrue(compareResult.isEqual()); 35 | } 36 | 37 | private Table createRowSpanningTable(PDImageXObject glider) { 38 | final Table.TableBuilder tableBuilder = Table.builder() 39 | .addColumnsOfWidth(50, 30, 80, 20); 40 | // First row 41 | tableBuilder.addRow( 42 | Row.builder() 43 | .add(ImageCell.builder().colSpan(2).image(glider).borderWidth(1).build()) 44 | .add(ImageCell.builder().colSpan(2).image(glider).borderWidth(1).build()) 45 | .build()); 46 | 47 | // Second row 48 | tableBuilder.addRow( 49 | Row.builder() 50 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 51 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 52 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 53 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 54 | .build()); 55 | 56 | // Third row 57 | tableBuilder.addRow( 58 | Row.builder() 59 | .add(ImageCell.builder().colSpan(4).scale(2f).image(glider).borderWidth(1).build()) 60 | .build()); 61 | 62 | return tableBuilder.build(); 63 | } 64 | 65 | private Table createColSpanningTable(PDImageXObject glider) { 66 | final Table.TableBuilder tableBuilder = Table.builder() 67 | .addColumnsOfWidth(100, 100); 68 | // First row 69 | tableBuilder.addRow( 70 | Row.builder() 71 | .add(TextCell.builder().rowSpan(2).text("Row span").borderWidth(1).build()) 72 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 73 | .build()); 74 | 75 | // Second row 76 | tableBuilder.addRow( 77 | Row.builder() 78 | .add(ImageCell.builder().image(glider).borderWidth(1).build()) 79 | .build()); 80 | return tableBuilder.build(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/custom/CustomCellWithCustomDrawerUsingLombokTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.custom; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import lombok.experimental.SuperBuilder; 6 | import org.apache.pdfbox.pdmodel.PDDocument; 7 | import org.apache.pdfbox.pdmodel.PDPage; 8 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 9 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 10 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 11 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 12 | import org.junit.Test; 13 | import org.vandeseer.TestUtils; 14 | import org.vandeseer.easytable.TableDrawer; 15 | import org.vandeseer.easytable.drawing.Drawer; 16 | import org.vandeseer.easytable.drawing.DrawingContext; 17 | import org.vandeseer.easytable.drawing.PositionedStyledText; 18 | import org.vandeseer.easytable.drawing.cell.TextCellDrawer; 19 | import org.vandeseer.easytable.structure.Row; 20 | import org.vandeseer.easytable.structure.Table; 21 | import org.vandeseer.easytable.structure.cell.TextCell; 22 | 23 | import java.io.IOException; 24 | 25 | import static junit.framework.TestCase.assertTrue; 26 | import static org.vandeseer.TestUtils.getActualPdfFor; 27 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 28 | 29 | public class CustomCellWithCustomDrawerUsingLombokTest { 30 | 31 | public static final String FILE_NAME = "customCellDrawer.pdf"; 32 | 33 | @SuperBuilder 34 | private static class MyCustomCell extends TextCell { 35 | 36 | @Override 37 | public Drawer getDrawer() { 38 | return new TextCellDrawer(this) { 39 | @Override 40 | protected void drawText(DrawingContext drawingContext, PositionedStyledText text) throws IOException { 41 | System.out.println("My custom drawer is called :-)"); 42 | super.drawText(drawingContext, text.toBuilder().text(text.getText().toUpperCase()).build()); 43 | } 44 | }; 45 | } 46 | 47 | } 48 | 49 | @Test 50 | public void testCustomCellDrawer() throws IOException { 51 | 52 | try (final PDDocument document = new PDDocument()) { 53 | 54 | final PDPage page = new PDPage(PDRectangle.A4); 55 | document.addPage(page); 56 | 57 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, page)) { 58 | 59 | TableDrawer.builder() 60 | .contentStream(contentStream) 61 | .table(createSimpleTable()) 62 | .startX(50) 63 | .startY(page.getMediaBox().getHeight() - 50) 64 | .build() 65 | .draw(); 66 | 67 | } 68 | 69 | document.save(TestUtils.TARGET_FOLDER + "/" + FILE_NAME); 70 | } 71 | 72 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 73 | assertTrue(compareResult.isEqual()); 74 | } 75 | 76 | private static Table createSimpleTable() { 77 | return Table.builder() 78 | .addColumnsOfWidth(100, 100, 100, 100) 79 | .fontSize(8) 80 | .font(new PDType1Font(Standard14Fonts.FontName.HELVETICA)) 81 | .addRow(Row.builder() 82 | .add(MyCustomCell.builder().borderWidth(1).text("One").build()) 83 | .add(MyCustomCell.builder().borderWidth(1).text("Two").build()) 84 | .add(MyCustomCell.builder().borderWidth(1).text("Three").build()) 85 | .add(MyCustomCell.builder().borderWidth(1).text("Four").build()) 86 | .build()) 87 | .build(); 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/FinalYTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 8 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 9 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 10 | import org.junit.Test; 11 | import org.vandeseer.TestUtils; 12 | import org.vandeseer.easytable.TableDrawer; 13 | import org.vandeseer.easytable.structure.Row; 14 | import org.vandeseer.easytable.structure.Table; 15 | import org.vandeseer.easytable.structure.cell.TextCell; 16 | 17 | import java.io.IOException; 18 | 19 | import static junit.framework.TestCase.assertTrue; 20 | import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; 21 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 22 | import static org.vandeseer.TestUtils.getActualPdfFor; 23 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 24 | 25 | public class FinalYTest { 26 | 27 | private static final String FILE_NAME = "finalY.pdf"; 28 | 29 | @Test 30 | public void testFinalY() throws IOException { 31 | 32 | try (final PDDocument document = new PDDocument()) { 33 | for (int i = 0; i < 10; i++) { 34 | final PDPage page = new PDPage(PDRectangle.A4); 35 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, page, APPEND, false)) { 36 | contentStream.beginText(); 37 | contentStream.setFont(new PDType1Font(HELVETICA), 12); 38 | contentStream.newLineAtOffset(100, 100); 39 | contentStream.showText("text number " + i); 40 | contentStream.endText(); 41 | } 42 | document.addPage(page); 43 | } 44 | 45 | for (int i = 0; i < document.getNumberOfPages(); i++) { 46 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, document.getPage(i), APPEND, false)) { 47 | TableDrawer tableDrawer = TableDrawer.builder() 48 | .contentStream(contentStream) 49 | .table(createHeaderTableForPage(i)) 50 | .startX(50) 51 | .startY(document.getPage(i).getMediaBox().getHeight() - 50) 52 | .build(); 53 | tableDrawer.draw(); 54 | 55 | contentStream.beginText(); 56 | contentStream.setFont(new PDType1Font(HELVETICA), 12); 57 | contentStream.newLineAtOffset(100, tableDrawer.getFinalY() - 12); 58 | contentStream.showText("text after final y " + tableDrawer.getFinalY()); 59 | contentStream.endText(); 60 | } 61 | } 62 | 63 | document.save(TestUtils.TARGET_FOLDER + "/" + FILE_NAME); 64 | } 65 | 66 | final CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 67 | assertTrue(compareResult.isEqual()); 68 | } 69 | 70 | private static Table createHeaderTableForPage(int pageNumber) { 71 | return Table.builder() 72 | .addColumnsOfWidth(100, 100, 100, 100) 73 | .fontSize(8) 74 | .font(new PDType1Font(HELVETICA)) 75 | .addRow(Row.builder() 76 | .add(TextCell.builder().borderWidth(1).text("Header").build()) 77 | .add(TextCell.builder().borderWidth(1).text("of").build()) 78 | .add(TextCell.builder().borderWidth(1).text("Page").build()) 79 | .add(TextCell.builder().borderWidth(1).text(String.valueOf(pageNumber)).build()) 80 | .build()) 81 | .build(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/OverflowOnSamePageRepeatableHeaderTableDrawerTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.vandeseer.TestUtils.getActualPdfFor; 5 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 6 | 7 | import java.awt.Color; 8 | import java.io.IOException; 9 | 10 | import org.apache.pdfbox.pdmodel.PDDocument; 11 | import org.apache.pdfbox.pdmodel.PDPage; 12 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 13 | import org.junit.Test; 14 | import org.vandeseer.TestUtils; 15 | import org.vandeseer.easytable.OverflowOnSamePageRepeatableHeaderTableDrawer; 16 | import org.vandeseer.easytable.structure.Row; 17 | import org.vandeseer.easytable.structure.Table; 18 | import org.vandeseer.easytable.structure.cell.TextCell; 19 | 20 | import de.redsix.pdfcompare.CompareResult; 21 | import de.redsix.pdfcompare.PdfComparator; 22 | 23 | public class OverflowOnSamePageRepeatableHeaderTableDrawerTest { 24 | private static final String OVERFLOW_ON_SAME_PAGE_REPEATABLE_HEADER_PDF = "overflowOnSamePageRepeatableHeader.pdf"; 25 | 26 | @Test 27 | public void createTableWithOverflowOnSamePageAndRepeatableHeader() throws IOException { 28 | 29 | try (final PDDocument document = new PDDocument()) { 30 | 31 | OverflowOnSamePageRepeatableHeaderTableDrawer.builder() 32 | .table(createTable()) 33 | .startX(50) 34 | .lanesPerPage(3) 35 | .numberOfRowsToRepeat(2) 36 | .spaceInBetween(25) 37 | .endY(50F) // note: if not set, table is drawn over the end of the page 38 | .build() 39 | .draw(() -> document, () -> new PDPage(PDRectangle.A4), 100f); 40 | 41 | document.save(TestUtils.TARGET_FOLDER + "/" + OVERFLOW_ON_SAME_PAGE_REPEATABLE_HEADER_PDF); 42 | } 43 | 44 | final CompareResult compareResult = new PdfComparator<>( 45 | getExpectedPdfFor(OVERFLOW_ON_SAME_PAGE_REPEATABLE_HEADER_PDF), 46 | getActualPdfFor(OVERFLOW_ON_SAME_PAGE_REPEATABLE_HEADER_PDF) 47 | ).compare(); 48 | assertTrue(compareResult.isEqual()); 49 | } 50 | 51 | private Table createTable() { 52 | final Table.TableBuilder tableBuilder = Table.builder() 53 | .addColumnOfWidth(60) 54 | .addColumnOfWidth(60); 55 | 56 | TextCell dummyHeaderCell = TextCell.builder() 57 | .text("HD") 58 | .backgroundColor(Color.BLUE) 59 | .textColor(Color.WHITE) 60 | .borderWidth(1F) 61 | .build(); 62 | 63 | TextCell dummyHeaderCell2 = TextCell.builder() 64 | .text("HD2") 65 | .backgroundColor(Color.BLACK) 66 | .textColor(Color.RED) 67 | .borderWidth(1F) 68 | .build(); 69 | 70 | tableBuilder.addRow( 71 | Row.builder() 72 | .add(dummyHeaderCell) 73 | .add(dummyHeaderCell) 74 | .build()); 75 | 76 | tableBuilder.addRow( 77 | Row.builder() 78 | .add(dummyHeaderCell2) 79 | .add(dummyHeaderCell2) 80 | .build()); 81 | 82 | for (int i = 0; i < 200; i++) { 83 | tableBuilder.addRow( 84 | Row.builder() 85 | .add(TextCell.builder() 86 | .text("a " + i) 87 | .borderWidth(1F) 88 | .build()) 89 | .add(TextCell.builder() 90 | .text("b " + i) 91 | .borderWidth(1F) 92 | .build()) 93 | .build()); 94 | } 95 | 96 | return tableBuilder.build(); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/StartPageTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 8 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 9 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.vandeseer.TestUtils; 13 | import org.vandeseer.easytable.TableDrawer; 14 | import org.vandeseer.easytable.structure.Row; 15 | import org.vandeseer.easytable.structure.Table; 16 | import org.vandeseer.easytable.structure.Table.TableBuilder; 17 | import org.vandeseer.easytable.structure.cell.TextCell; 18 | 19 | import java.io.IOException; 20 | 21 | import static junit.framework.TestCase.assertEquals; 22 | import static junit.framework.TestCase.assertTrue; 23 | import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; 24 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 25 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.TIMES_ROMAN; 26 | import static org.vandeseer.TestUtils.getActualPdfFor; 27 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 28 | 29 | public class StartPageTest { 30 | 31 | private static final String FULL_TABLE_EXISTING_PAGE_FILE_NAME = "fullTableExistingPage.pdf"; 32 | private static final String START_TABLE_EXISTING_PAGE_FILE_NAME = "startTableExistingPage.pdf"; 33 | private static final String START_TABLE_NEW_PAGE_FILE_NAME = "startTableNewPage.pdf"; 34 | 35 | private Table table; 36 | 37 | private PDDocument document; 38 | 39 | private PDPage currentPage; 40 | 41 | private TableDrawer drawer; 42 | 43 | @Before 44 | public void before() throws IOException { 45 | TestUtils.assertRegressionFolderExists(); 46 | 47 | document = new PDDocument(); 48 | currentPage = new PDPage(PDRectangle.A4); 49 | document.addPage(currentPage); 50 | 51 | try (final PDPageContentStream content = new PDPageContentStream(document, currentPage, APPEND, false)) { 52 | content.beginText(); 53 | content.setFont(new PDType1Font(TIMES_ROMAN), 15); 54 | content.newLineAtOffset(50, 500); 55 | content.showText("This line is added to ensure table is drawn on new page when space not available."); 56 | content.endText(); 57 | content.close(); 58 | 59 | TableBuilder builder = Table.builder().addColumnsOfWidth(150, 150, 150) 60 | .fontSize(25).font(new PDType1Font(HELVETICA)) 61 | .padding(5).borderWidth(1) 62 | .addRow(Row.builder().add(TextCell.builder().text("Header").build()) 63 | .add(TextCell.builder().text("of").build()).add(TextCell.builder().text("Table").build()) 64 | .build()); 65 | 66 | for (int i = 1; i < 5; i++) { 67 | builder.addRow(Row.builder().add(TextCell.builder().text("Row " + i).build()) 68 | .add(TextCell.builder().text("of").build()).add(TextCell.builder().text("Table").build()) 69 | .build()).build(); 70 | } 71 | table = builder.build(); 72 | } 73 | } 74 | 75 | private void createTable(float startY, String outputFileName) throws IOException { 76 | 77 | drawer = TableDrawer.builder().table(table).startX(50f).startY(startY).endY(50F).build(); 78 | 79 | drawer.draw(() -> document, () -> new PDPage(PDRectangle.A4), 50f); 80 | 81 | document.save(TestUtils.TARGET_FOLDER + "/" + outputFileName); 82 | document.close(); 83 | 84 | final CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(outputFileName), 85 | getActualPdfFor(outputFileName)).compare(); 86 | 87 | assertTrue(compareResult.isEqual()); 88 | } 89 | 90 | @Test 91 | public void createCompleteTableInExistingPage() throws IOException { 92 | createTable(200f, FULL_TABLE_EXISTING_PAGE_FILE_NAME); 93 | assertEquals(currentPage, drawer.getTableStartPage()); 94 | } 95 | 96 | @Test 97 | public void createSplitTableStartInExistingPage() throws IOException { 98 | createTable(120f, START_TABLE_EXISTING_PAGE_FILE_NAME); 99 | assertEquals(currentPage, drawer.getTableStartPage()); 100 | } 101 | 102 | @Test 103 | public void createTableStartInNewPage() throws IOException { 104 | createTable(70f, START_TABLE_NEW_PAGE_FILE_NAME); 105 | assertEquals(document.getPage(document.getNumberOfPages() - 1), drawer.getTableStartPage()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/TestUtils.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer; 2 | 3 | import org.apache.pdfbox.io.IOUtils; 4 | import org.apache.pdfbox.pdmodel.PDDocument; 5 | import org.apache.pdfbox.pdmodel.PDPage; 6 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 7 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 8 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 9 | import org.vandeseer.easytable.TableDrawer; 10 | import org.vandeseer.easytable.structure.Table; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.util.Collection; 15 | import java.util.Objects; 16 | 17 | public class TestUtils { 18 | 19 | public static final String TARGET_FOLDER = "target"; 20 | public static final String TARGET_SUBFOLDER_REGRESSION = "/regression"; 21 | 22 | public static final String REFERENCE_FOLDER = "src/test/reference/"; 23 | 24 | private static final float PADDING = 50f; 25 | 26 | private static final PDDocument PD_DOCUMENT_FOR_IMAGES = new PDDocument(); 27 | 28 | private TestUtils() { 29 | } 30 | 31 | public static void createAndSaveDocumentWithTable(String outputFileName, Table table) throws IOException { 32 | createAndSaveDocumentWithTables(outputFileName, table); 33 | } 34 | 35 | public static void createAndSaveDocumentWithTables(String outputFileName, Collection tables) throws IOException { 36 | createAndSaveDocumentWithTables(new PDDocument(), outputFileName, tables.toArray(new Table[0])); 37 | } 38 | 39 | public static void createAndSaveDocumentWithTables(String outputFileName, Table... tables) throws IOException { 40 | createAndSaveDocumentWithTables(new PDDocument(), outputFileName, tables); 41 | } 42 | 43 | private static void createAndSaveDocumentWithTables(PDDocument document, String outputFileName, Table... tables) throws IOException { 44 | 45 | final PDPage page = new PDPage(PDRectangle.A4); 46 | document.addPage(page); 47 | 48 | float startY = page.getMediaBox().getHeight() - PADDING; 49 | 50 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, page)) { 51 | 52 | for (final Table table : tables) { 53 | 54 | TableDrawer.builder() 55 | .page(page) 56 | .contentStream(contentStream) 57 | .table(table) 58 | .startX(PADDING) 59 | .startY(startY) 60 | .endY(PADDING) 61 | .build() 62 | .draw(() -> document, () -> new PDPage(PDRectangle.A4), PADDING); 63 | 64 | startY -= (table.getHeight() + PADDING); 65 | } 66 | 67 | } 68 | 69 | document.save(TARGET_FOLDER + "/" + outputFileName); 70 | document.close(); 71 | 72 | } 73 | 74 | public static PDImageXObject createTuxImage() throws IOException { 75 | final byte[] tuxBytes = IOUtils.toByteArray(Objects.requireNonNull(TestUtils.class.getClassLoader().getResourceAsStream("tux.png"))); 76 | return PDImageXObject.createFromByteArray(PD_DOCUMENT_FOR_IMAGES, tuxBytes, "tux"); 77 | } 78 | 79 | public static PDImageXObject createGliderImage() throws IOException { 80 | final byte[] gliderBytes = IOUtils.toByteArray(Objects.requireNonNull(TestUtils.class.getClassLoader().getResourceAsStream("glider.png"))); 81 | return PDImageXObject.createFromByteArray(PD_DOCUMENT_FOR_IMAGES, gliderBytes, "glider"); 82 | } 83 | 84 | public static PDImageXObject createSampleImage() throws IOException { 85 | final byte[] sampleBytes = IOUtils.toByteArray(Objects.requireNonNull(TestUtils.class.getClassLoader().getResourceAsStream("sample.png"))); 86 | return PDImageXObject.createFromByteArray(PD_DOCUMENT_FOR_IMAGES, sampleBytes, "sample"); 87 | } 88 | 89 | public static void assertRegressionFolderExists() { 90 | new File(TARGET_FOLDER + TARGET_SUBFOLDER_REGRESSION).mkdirs(); 91 | } 92 | 93 | public static String getRegressionFolder() { 94 | return TARGET_FOLDER + TARGET_SUBFOLDER_REGRESSION; 95 | } 96 | 97 | public static File getActualPdfFor(String fileName) { 98 | return new File(TARGET_FOLDER + "/" + fileName); 99 | } 100 | 101 | public static File getExpectedPdfFor(final String fileName) { 102 | return new File(REFERENCE_FOLDER + fileName); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/regressiontest/Issue28InfiniteLoopTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.regressiontest; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 6 | import org.junit.Test; 7 | import org.vandeseer.TestUtils; 8 | import org.vandeseer.easytable.RowIsTooHighException; 9 | import org.vandeseer.easytable.TableDrawer; 10 | import org.vandeseer.easytable.structure.Row; 11 | import org.vandeseer.easytable.structure.Table; 12 | import org.vandeseer.easytable.structure.cell.TextCell; 13 | 14 | import java.awt.*; 15 | import java.io.IOException; 16 | 17 | public class Issue28InfiniteLoopTest { 18 | 19 | @Test(expected = RowIsTooHighException.class) 20 | public void createTwoPageTable() throws IOException { 21 | final Table.TableBuilder tableBuilder = Table.builder() 22 | .addColumnOfWidth(200) 23 | .addColumnOfWidth(200); 24 | 25 | TextCell dummyHeaderCell = TextCell.builder() 26 | .text("Header dummy") 27 | .backgroundColor(Color.BLUE) 28 | .textColor(Color.WHITE) 29 | .borderWidth(1F) 30 | .build(); 31 | 32 | String longText = "Sed turpis nisl, ullamcorper vitae ornare eget, porta ac odio. Nunc lacinia convallis urna " + 33 | "sit amet scelerisque. Morbi neque est, tempor sit amet sagittis in, luctus et nisl. " + 34 | "Phasellus ut mollis felis. Sed viverra tortor in ligula ultricies, quis faucibus turpis varius. " + 35 | "Duis ante leo, ornare sed lectus in, finibus placerat est. Pellentesque habitant morbi " + 36 | "tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus rhoncus felis " + 37 | "sit amet ex consequat aliquam. Ut euismod odio iaculis vulputate malesuada. " + 38 | "Donec euismod ipsum id erat ullamcorper elementum. Aenean malesuada mattis libero ac pretium. " + 39 | "Nam non tempor risus, in congue turpis. Nam mi dolor, gravida ac imperdiet quis, pulvinar sed metus. " + 40 | "Sed tempor orci magna, non aliquet nisl posuere vitae. Cras erat felis, euismod et sodales cursus, " + 41 | "fermentum at arcu. Aenean maximus magna vel dignissim ullamcorper.Sed turpis nisl, ullamcorper " + 42 | "vitae ornare eget, porta ac odio. Nunc lacinia convallis urna sit amet scelerisque. " + 43 | "Morbi neque est, tempor sit amet sagittis in, luctus et nisl. Phasellus ut mollis felis. " + 44 | "Sed viverra tortor in ligula ultricies, quis faucibus turpis varius. Duis ante leo, ornare sed lectus in, " + 45 | "finibus placerat est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac " + 46 | "turpis egestas. Phasellus rhoncus felis sit amet ex consequat aliquam. Ut euismod odio iaculis " + 47 | "vulputate malesuada. Donec euismod ipsum id erat ullamcorper elementum." + 48 | "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac " + 49 | "turpis egestas. Phasellus rhoncus felis sit amet ex consequat aliquam. Ut euismod odio iaculis " + 50 | "vulputate malesuada. Donec euismod ipsum id erat ullamcorper elementum."; 51 | 52 | TextCell dummyCell = TextCell.builder() 53 | .text("dummy") 54 | .borderWidth(1F) 55 | .build(); 56 | 57 | 58 | TextCell bigCell = TextCell.builder() 59 | .text(longText) 60 | .borderWidth(1F) 61 | .build(); 62 | 63 | tableBuilder.addRow( 64 | Row.builder() 65 | .add(dummyHeaderCell) 66 | .add(dummyHeaderCell) 67 | .build()); 68 | 69 | 70 | tableBuilder.addRow( 71 | Row.builder() 72 | .add(dummyCell) 73 | .add(bigCell) 74 | .build()); 75 | 76 | 77 | final PDDocument document = new PDDocument(); 78 | 79 | TableDrawer drawer = TableDrawer.builder() 80 | .table(tableBuilder.build()) 81 | .startX(50F) 82 | .startY(50F) 83 | .endY(50F) // note: if not set, table is drawn over the end of the page 84 | .build(); 85 | 86 | drawer.draw(() -> document, () -> new PDPage(PDRectangle.A4), 50f); 87 | 88 | document.save(TestUtils.TARGET_FOLDER + TestUtils.TARGET_SUBFOLDER_REGRESSION + "/twoPageTable.pdf"); 89 | document.close(); 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/cell/TextCellDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing.cell; 2 | 3 | import lombok.NoArgsConstructor; 4 | import lombok.SneakyThrows; 5 | import org.apache.pdfbox.pdmodel.font.PDFont; 6 | import org.vandeseer.easytable.drawing.DrawingContext; 7 | import org.vandeseer.easytable.drawing.DrawingUtil; 8 | import org.vandeseer.easytable.drawing.PositionedStyledText; 9 | import org.vandeseer.easytable.structure.cell.AbstractTextCell; 10 | import org.vandeseer.easytable.util.PdfUtil; 11 | 12 | import java.awt.*; 13 | import java.io.IOException; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import static org.vandeseer.easytable.settings.HorizontalAlignment.*; 18 | 19 | @NoArgsConstructor 20 | public class TextCellDrawer extends AbstractCellDrawer { 21 | 22 | public TextCellDrawer(T cell) { 23 | this.cell = cell; 24 | } 25 | 26 | @Override 27 | @SneakyThrows 28 | public void drawContent(DrawingContext drawingContext) { 29 | final float startX = drawingContext.getStartingPoint().x; 30 | 31 | final PDFont currentFont = cell.getFont(); 32 | final int currentFontSize = cell.getFontSize(); 33 | final Color currentTextColor = cell.getTextColor(); 34 | 35 | float yOffset = drawingContext.getStartingPoint().y + getAdaptionForVerticalAlignment(); 36 | float xOffset = startX + cell.getPaddingLeft(); 37 | 38 | final List lines = calculateAndGetLines(currentFont, currentFontSize, cell.getMaxWidth()); 39 | for (int i = 0; i < lines.size(); i++) { 40 | final String line = lines.get(i); 41 | 42 | yOffset -= calculateYOffset(currentFont, currentFontSize, i); 43 | 44 | final float textWidth = PdfUtil.getStringWidth(line, currentFont, currentFontSize); 45 | 46 | // Handle horizontal alignment by adjusting the xOffset 47 | if (cell.isHorizontallyAligned(RIGHT)) { 48 | xOffset = startX + (cell.getWidth() - (textWidth + cell.getPaddingRight())); 49 | 50 | } else if (cell.isHorizontallyAligned(CENTER)) { 51 | xOffset = startX + (cell.getWidth() - textWidth) / 2; 52 | 53 | } else if (cell.isHorizontallyAligned(JUSTIFY) && isNotLastLine(lines, i)) { 54 | drawingContext.getContentStream().setCharacterSpacing(calculateCharSpacingFor(line)); 55 | } 56 | 57 | drawText( 58 | drawingContext, 59 | PositionedStyledText.builder() 60 | .x(xOffset) 61 | .y(yOffset) 62 | .text(line) 63 | .font(currentFont) 64 | .fontSize(currentFontSize) 65 | .color(currentTextColor) 66 | .build() 67 | ); 68 | } 69 | } 70 | 71 | @Override 72 | protected float calculateInnerHeight() { 73 | return cell.getTextHeight(); 74 | } 75 | 76 | 77 | private float calculateYOffset(PDFont currentFont, int currentFontSize, int lineIndex) { 78 | return PdfUtil.getFontHeight(currentFont, currentFontSize) // font height 79 | + (lineIndex > 0 ? PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing() : 0f); // line spacing 80 | } 81 | 82 | static boolean isNotLastLine(List lines, int i) { 83 | return i != lines.size() - 1; 84 | } 85 | 86 | // Code from https://stackoverflow.com/questions/20680430/is-it-possible-to-justify-text-in-pdfbox 87 | protected float calculateCharSpacingFor(String line) { 88 | float charSpacing = 0; 89 | if (line.length() > 1) { 90 | float size = PdfUtil.getStringWidth(line, cell.getFont(), cell.getFontSize()); 91 | float free = cell.getWidthOfText() - size; 92 | if (free > 0) { 93 | charSpacing = free / (line.length() - 1); 94 | } 95 | } 96 | return charSpacing; 97 | } 98 | 99 | protected List calculateAndGetLines(PDFont currentFont, int currentFontSize, float maxWidth) { 100 | return cell.isWordBreak() 101 | ? PdfUtil.getOptimalTextBreakLines(cell.getText(), currentFont, currentFontSize, maxWidth) 102 | : Collections.singletonList(cell.getText()); 103 | } 104 | 105 | protected void drawText(DrawingContext drawingContext, PositionedStyledText positionedStyledText) throws IOException { 106 | DrawingUtil.drawText( 107 | drawingContext.getContentStream(), 108 | positionedStyledText 109 | ); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/cell/AbstractTextCell.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.experimental.SuperBuilder; 6 | import org.apache.pdfbox.pdmodel.font.PDFont; 7 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 8 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 9 | import org.vandeseer.easytable.structure.Column; 10 | import org.vandeseer.easytable.util.PdfUtil; 11 | 12 | import java.awt.*; 13 | import java.util.Comparator; 14 | import java.util.List; 15 | 16 | @Getter 17 | @SuperBuilder(toBuilder = true) 18 | public abstract class AbstractTextCell extends AbstractCell { 19 | 20 | protected Color textColor; 21 | 22 | @Builder.Default 23 | protected float lineSpacing = 1f; 24 | 25 | public PDFont getFont() { 26 | return settings.getFont(); 27 | } 28 | 29 | public Integer getFontSize() { 30 | return settings.getFontSize(); 31 | } 32 | 33 | public Color getTextColor() { 34 | return settings.getTextColor(); 35 | } 36 | 37 | private Float textHeight; 38 | 39 | public abstract String getText(); 40 | 41 | @Override 42 | public float getMinHeight() { 43 | return (getVerticalPadding() + getTextHeight()) > super.getMinHeight() 44 | ? (getVerticalPadding() + getTextHeight()) 45 | : super.getMinHeight(); 46 | } 47 | 48 | /** 49 | * Calculates the cell's text's height in respect when horizontally printed/rendered. 50 | * 51 | * @return the height of the cell's text taking into account spacing and line breaks 52 | */ 53 | public float getTextHeight() { 54 | 55 | if (this.textHeight != null) { 56 | return this.textHeight; 57 | } 58 | 59 | this.textHeight = PdfUtil.getFontHeight(getFont(), getFontSize()); 60 | 61 | if (settings.isWordBreak()) { 62 | 63 | final int size = PdfUtil.getOptimalTextBreakLines(getText(), getFont(), getFontSize(), getMaxWidth()).size(); 64 | 65 | final float heightOfTextLines = size * this.textHeight; 66 | final float heightOfLineSpacing = (size - 1) * this.textHeight * getLineSpacing(); 67 | 68 | this.textHeight = heightOfTextLines + heightOfLineSpacing; 69 | } 70 | 71 | return this.textHeight; 72 | } 73 | 74 | public float getWidthOfText() { 75 | assertIsRendered(); 76 | 77 | final float notBrokenTextWidth = PdfUtil.getStringWidth(getText(), getFont(), getFontSize()); 78 | 79 | if (settings.isWordBreak()) { 80 | 81 | final float maxWidth = getMaxWidthOfText() - getHorizontalPadding(); 82 | List textLines = PdfUtil.getOptimalTextBreakLines(getText(), getFont(), getFontSize(), maxWidth); 83 | 84 | return textLines.stream() 85 | .map(line -> PdfUtil.getStringWidth(line, getFont(), getFontSize())) 86 | .max(Comparator.naturalOrder()) 87 | .orElse(notBrokenTextWidth); 88 | 89 | } 90 | 91 | return notBrokenTextWidth; 92 | } 93 | 94 | private float getMaxWidthOfText() { 95 | float columnsWidth = getColumn().getWidth(); 96 | 97 | // We have to take column spanning into account 98 | if (getColSpan() > 1) { 99 | Column currentColumn = getColumn(); 100 | for (int i = 1; i < getColSpan(); i++) { 101 | columnsWidth += currentColumn.getNext().getWidth(); 102 | currentColumn = currentColumn.getNext(); 103 | } 104 | } 105 | return columnsWidth; 106 | } 107 | 108 | public float getMaxWidth() { 109 | return getMaxWidthOfText() - getHorizontalPadding(); 110 | } 111 | 112 | // Adaption for Lombok 113 | public abstract static class AbstractTextCellBuilder> extends AbstractCellBuilder { 114 | 115 | public B font(final PDFont font) { 116 | settings.setFont(font); 117 | return this.self(); 118 | } 119 | 120 | // Convenience setter for being halfway backwards compatible 121 | public B font(Standard14Fonts.FontName fontName) { 122 | settings.setFont(new PDType1Font(fontName)); 123 | return this.self(); 124 | } 125 | 126 | public B fontSize(final Integer fontSize) { 127 | settings.setFontSize(fontSize); 128 | return this.self(); 129 | } 130 | 131 | public B textColor(final Color textColor) { 132 | settings.setTextColor(textColor); 133 | return this.self(); 134 | } 135 | 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/image/ImageCellAlignmentTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.image; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDPage; 6 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 7 | import org.junit.Test; 8 | import org.vandeseer.TestUtils; 9 | import org.vandeseer.easytable.settings.HorizontalAlignment; 10 | import org.vandeseer.easytable.settings.VerticalAlignment; 11 | import org.vandeseer.easytable.structure.Row; 12 | import org.vandeseer.easytable.structure.Table; 13 | import org.vandeseer.easytable.structure.cell.ImageCell; 14 | import org.vandeseer.easytable.structure.cell.TextCell; 15 | 16 | import java.io.IOException; 17 | 18 | import static junit.framework.TestCase.assertTrue; 19 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 20 | import static org.vandeseer.TestUtils.getActualPdfFor; 21 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 22 | import static org.vandeseer.easytable.settings.HorizontalAlignment.*; 23 | import static org.vandeseer.easytable.settings.VerticalAlignment.*; 24 | 25 | public class ImageCellAlignmentTest { 26 | 27 | public static final String FILE_NAME = "imageCellAlignment.pdf"; 28 | 29 | @Test 30 | public void test() throws IOException { 31 | TestUtils.createAndSaveDocumentWithTables(FILE_NAME, 32 | createTableWithImageCellThatHasImageAligned(TOP, LEFT), 33 | createTableWithImageCellThatHasImageAligned(TOP, RIGHT), 34 | createTableWithImageCellThatHasImageAligned(MIDDLE, CENTER), 35 | createTableWithImageCellThatHasImageAligned(MIDDLE, RIGHT), 36 | createTableWithImageCellThatHasImageAligned(BOTTOM, LEFT), 37 | createTableWithImageCellThatHasImageAligned(BOTTOM, CENTER) 38 | ); 39 | 40 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 41 | assertTrue(compareResult.isEqual()); 42 | } 43 | 44 | private Table createTableWithImageCellThatHasImageAligned(VerticalAlignment verticalAlignment, HorizontalAlignment horizontalAlignment) throws IOException { 45 | float userPageWidth = new PDPage().getMediaBox().getWidth() - (2.0F * 72.0f); 46 | float columnWidth = userPageWidth / 8.0F; 47 | 48 | Table.TableBuilder tableBuilder = Table.builder() 49 | .addColumnsOfWidth(columnWidth, columnWidth, columnWidth, columnWidth, 50 | columnWidth, columnWidth, columnWidth, columnWidth) 51 | .fontSize(8) 52 | .font(new PDType1Font(HELVETICA)) 53 | .wordBreak(true); 54 | 55 | Row.RowBuilder rowBuilder = Row.builder() 56 | .add(TextCell.builder() 57 | .borderWidth(1) 58 | .padding(4) 59 | .text("Cell 1") 60 | .build()) 61 | .add(TextCell.builder() 62 | .borderWidth(1) 63 | .padding(4) 64 | .text("Cell 2") 65 | .build()) 66 | .add(TextCell.builder() 67 | .borderWidth(1) 68 | .padding(4) 69 | .text("Cell 3") 70 | .build()) 71 | .add(TextCell.builder() 72 | .colSpan(4) 73 | .borderWidth(1) 74 | .padding(4) 75 | .colSpan(2) 76 | .text("Cell 4\nMore text\nEven more text\nEven more text\n" + 77 | "Even more text\nEven more text\nEven more text") 78 | .build()) 79 | .add(TextCell.builder() 80 | .borderWidth(1) 81 | .padding(4) 82 | .text(verticalAlignment.toString() + ", " + horizontalAlignment.toString()) 83 | .build()) 84 | .add(ImageCell.builder() 85 | .verticalAlignment(verticalAlignment) 86 | .horizontalAlignment(horizontalAlignment) 87 | .borderWidth(1) 88 | .padding(2) 89 | .image(TestUtils.createSampleImage()) 90 | .scale(0.2f) 91 | .colSpan(2) 92 | .build()); 93 | 94 | tableBuilder.addRow(rowBuilder.font(new PDType1Font(HELVETICA)) 95 | .fontSize(8) 96 | .horizontalAlignment(CENTER) 97 | .build()); 98 | 99 | return tableBuilder.build(); 100 | } 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/regressiontest/Issue111RepeatHeaderTableTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.regressiontest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 8 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 9 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.vandeseer.TestUtils; 13 | import org.vandeseer.easytable.RepeatedHeaderTableDrawer; 14 | import org.vandeseer.easytable.settings.HorizontalAlignment; 15 | import org.vandeseer.easytable.settings.VerticalAlignment; 16 | import org.vandeseer.easytable.structure.Row; 17 | import org.vandeseer.easytable.structure.Table; 18 | import org.vandeseer.easytable.structure.Table.TableBuilder; 19 | import org.vandeseer.easytable.structure.cell.TextCell; 20 | 21 | import java.awt.*; 22 | import java.io.IOException; 23 | 24 | import static junit.framework.TestCase.assertTrue; 25 | import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; 26 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.TIMES_ROMAN; 27 | import static org.vandeseer.TestUtils.getActualPdfFor; 28 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 29 | 30 | public class Issue111RepeatHeaderTableTest { 31 | 32 | private static final String FILE_NAME_TEST_1 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" 33 | + "repeatHeaderTable_issue111.pdf"; 34 | private static final String FILE_NAME_TEST_2 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" 35 | + "repeatHeaderTable_issue111_2.pdf"; 36 | private static final String FILE_NAME_TEST_3 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" 37 | + "repeatHeaderTable_issue111_3.pdf"; 38 | private static final String FILE_NAME_TEST_4 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" 39 | + "repeatHeaderTable_issue111_4.pdf"; 40 | 41 | private Table table; 42 | 43 | private PDDocument document; 44 | 45 | @Before 46 | public void before() throws IOException { 47 | TestUtils.assertRegressionFolderExists(); 48 | 49 | document = new PDDocument(); 50 | PDPage my_page = new PDPage(PDRectangle.A4); 51 | document.addPage(my_page); 52 | 53 | PDPageContentStream content = new PDPageContentStream(document, my_page, APPEND, true); 54 | content.beginText(); 55 | content.setFont(new PDType1Font(TIMES_ROMAN), 15); 56 | content.newLineAtOffset(50, 500); 57 | content.showText("This line is added to ensure table is drawn on new page when space not available."); 58 | content.endText(); 59 | content.close(); 60 | 61 | TableBuilder builder = Table.builder().addColumnsOfWidth(100, 400).borderColor(Color.LIGHT_GRAY).borderWidth(1) 62 | .font(new PDType1Font(TIMES_ROMAN)).fontSize(13).verticalAlignment(VerticalAlignment.TOP) 63 | .horizontalAlignment(HorizontalAlignment.LEFT).padding(5) 64 | 65 | .addRow(Row.builder().add(TextCell.builder().text("Repeat Row 1").borderWidth(0).colSpan(2).build()) 66 | .build()) 67 | .addRow(Row.builder().add(TextCell.builder().text("Repeat Row 2").borderWidth(0).colSpan(2).build()) 68 | .build()); 69 | 70 | for (int i = 1; i < 5; i++) { 71 | builder.addRow(Row.builder().add(TextCell.builder().text(String.valueOf(i)).build()) 72 | .add(TextCell.builder() 73 | .text("EASYTABLE PDFBoX EASYTABLE PDFBoX EASYTABLE PDFBoX EASYTABLE PDFBoX EASYTABLE PDFBoX" 74 | + "EASYTABLE PDFBoX EASYTABLE PDFBoX EASYTABLE PDFBoX") 75 | .lineSpacing(1).build()) 76 | .build()); 77 | } 78 | table = builder.build(); 79 | } 80 | 81 | @Test 82 | public void createTableWhenPageHeightForHeadersAndOneDataRow() throws IOException { 83 | 84 | createTable(150f, FILE_NAME_TEST_1); 85 | } 86 | 87 | @Test 88 | public void createTableInNewPageWhenPageHeightForHeadersOnly() throws IOException { 89 | 90 | createTable(100f, FILE_NAME_TEST_2); 91 | } 92 | 93 | @Test 94 | public void createTableInNewPageWhenPageHeightForOneHeaderOnly() throws IOException { 95 | 96 | createTable(70f, FILE_NAME_TEST_3); 97 | } 98 | 99 | // Checks if any extra space added to top of table in new page 100 | @Test 101 | public void createTableInNewPageWhenPageHeightForNoHeaders() throws IOException { 102 | 103 | createTable(30f, FILE_NAME_TEST_4); 104 | } 105 | 106 | private void createTable(float startY, String outputFileName) throws IOException { 107 | 108 | RepeatedHeaderTableDrawer.builder().table(table).startX(50f).startY(startY).endY(50F).numberOfRowsToRepeat(2) 109 | .build().draw(() -> document, () -> new PDPage(PDRectangle.A4), 50f); 110 | 111 | document.save(TestUtils.TARGET_FOLDER + "/" + outputFileName); 112 | document.close(); 113 | 114 | final CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(outputFileName), 115 | getActualPdfFor(outputFileName)).compare(); 116 | 117 | assertTrue(compareResult.isEqual()); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/easytable/util/PdfUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.util; 2 | 3 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 10 | import static org.hamcrest.CoreMatchers.equalTo; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.is; 13 | import static org.vandeseer.easytable.util.FloatUtil.isEqualInEpsilon; 14 | 15 | public class PdfUtilTest { 16 | 17 | @Test 18 | public void getStringWidth_noNewLines() { 19 | final String text = "this is a small text"; 20 | 21 | final float actualSize = PdfUtil.getStringWidth(text, new PDType1Font(HELVETICA), 12); 22 | final float expectedSize = 94.692F; 23 | 24 | assertThat(isEqualInEpsilon(expectedSize, actualSize), is(true)); 25 | } 26 | 27 | 28 | 29 | @Test 30 | public void getStringWidth_withNewLines_shouldReturnWidthOfLongestLine() { 31 | final String text = "this is a longer text\nthat has two\nnew lines in it"; 32 | final float firstLineWidth = PdfUtil.getStringWidth("this is a longer text", new PDType1Font(HELVETICA), 12); 33 | 34 | assertThat(PdfUtil.getStringWidth(text, new PDType1Font(HELVETICA), 12), equalTo(firstLineWidth)); 35 | } 36 | 37 | @Test 38 | public void getOptimalTextBreakLines_noNewLinesAndFitsInColumn_shouldReturnOneLine() { 39 | final String text = "this is a small text"; 40 | 41 | // We don't have to break in case we have two times the size ;) 42 | final float maxWidth = 2f * PdfUtil.getStringWidth(text, new PDType1Font(HELVETICA), 12); 43 | 44 | assertThat(PdfUtil.getOptimalTextBreakLines(text, new PDType1Font(HELVETICA), 12, maxWidth).size(), is(1)); 45 | } 46 | 47 | @Test 48 | public void getOptimalTextBreakLines_withNewLinesAndFitsInColumn_shouldReturnMoreThanOneLine() { 49 | final String text = "this is a small text\nthat has two\nnew lines in it"; 50 | 51 | // Since we have new lines 52 | final float maxWidth = 2f * PdfUtil.getStringWidth(text, new PDType1Font(HELVETICA), 12); 53 | 54 | assertThat(PdfUtil.getOptimalTextBreakLines(text, new PDType1Font(HELVETICA), 12, maxWidth).size(), is(3)); 55 | } 56 | 57 | @Test 58 | public void getOptimalTextBreakLines_noSpacesInText_shouldSplitOnDot() { 59 | final String text = "This.should.be.splitted.on.a.dot.No.spaces.in.here."; 60 | 61 | final float maxWidth = PdfUtil.getStringWidth("This.should.be.splitted.on.a.dot.", new PDType1Font(HELVETICA), 12); 62 | 63 | final List lines = PdfUtil.getOptimalTextBreakLines(text, new PDType1Font(HELVETICA), 12, maxWidth); 64 | 65 | assertThat(lines.size(), is(2)); 66 | assertThat(lines.get(0), is(equalTo("This.should.be.splitted.on.a.dot."))); 67 | assertThat(lines.get(1), is(equalTo("No.spaces.in.here."))); 68 | } 69 | 70 | @Test 71 | public void getOptimalTextBreakLines_noSpacesNorDotsInText_shouldSplitOnComma() { 72 | final String text = "This,should,be,splitted,on,a,comma,no,space,nor,dots,in,here,"; 73 | 74 | final float maxWidth = PdfUtil.getStringWidth("This,should,be,splitted,on,a,comma,", new PDType1Font(HELVETICA), 12); 75 | 76 | final List lines = PdfUtil.getOptimalTextBreakLines(text, new PDType1Font(HELVETICA), 12, maxWidth); 77 | 78 | assertThat(lines.size(), is(2)); 79 | assertThat(lines.get(0), is(equalTo("This,should,be,splitted,on,a,comma,"))); 80 | assertThat(lines.get(1), is(equalTo("no,space,nor,dots,in,here,"))); 81 | } 82 | 83 | @Test 84 | public void getOptimalTextBreakLines_noSpacesNorDotsNorCommasInText_shouldSplitBySize() { 85 | final String text = "ThisDoesNotHaveAnyCharactersWhereWeCouldBreakMoreEasilySoWeBreakBySize"; 86 | 87 | final float maxWidth = PdfUtil.getStringWidth("ThisDoesNotHaveAnyCharacters", new PDType1Font(HELVETICA), 12); 88 | final List lines = PdfUtil.getOptimalTextBreakLines(text, new PDType1Font(HELVETICA), 12, maxWidth); 89 | 90 | assertThat(lines.size(), is(3)); 91 | assertThat(lines.get(0), is(equalTo("ThisDoesNotHaveAnyCharacter-"))); 92 | assertThat(lines.get(1), is(equalTo("sWhereWeCouldBreakMoreEasi-"))); 93 | assertThat(lines.get(2), is(equalTo("lySoWeBreakBySize"))); 94 | } 95 | 96 | 97 | @Test(timeout = 5000L) 98 | public void testVeryBigText() { 99 | final StringBuilder builder = new StringBuilder(); 100 | final List expectedOutput = new ArrayList<>(); 101 | 102 | for (int i = 0; i < 50; i++) { 103 | builder.append("https://averylonginternetdnsnamewhich-maybe-breaks-easytable.com "); 104 | 105 | // optimal text-break 106 | expectedOutput.add("https://averylonginternetdns-"); 107 | expectedOutput.add("namewhich-maybe-breaks-"); 108 | expectedOutput.add("easytable.com"); 109 | } 110 | 111 | final List actualOutput = PdfUtil.getOptimalTextBreakLines(builder.toString(), new PDType1Font(HELVETICA), 8, 102); 112 | 113 | assertThat(actualOutput, equalTo(expectedOutput)); 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/Column.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.apache.pdfbox.pdmodel.font.PDFont; 7 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 8 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 9 | import org.vandeseer.easytable.settings.BorderStyleInterface; 10 | import org.vandeseer.easytable.settings.HorizontalAlignment; 11 | import org.vandeseer.easytable.settings.Settings; 12 | import org.vandeseer.easytable.settings.VerticalAlignment; 13 | 14 | import java.awt.*; 15 | 16 | @Getter 17 | @Setter(AccessLevel.PACKAGE) 18 | public class Column { 19 | 20 | private Table table; 21 | 22 | private Column next; 23 | 24 | private float width; 25 | 26 | @Setter(AccessLevel.NONE) 27 | private Settings settings; 28 | 29 | Column(final float width) { 30 | if (width < 0) { 31 | throw new IllegalArgumentException("Column width must be non-negative"); 32 | } 33 | this.width = width; 34 | this.settings = Settings.builder().build(); 35 | } 36 | 37 | private Column(final float width, final Settings settings) { 38 | this(width); 39 | this.settings = settings; 40 | } 41 | 42 | public static ColumnBuilder builder() { 43 | return new ColumnBuilder(); 44 | } 45 | 46 | boolean hasNext() { 47 | return next != null; 48 | } 49 | 50 | 51 | public static class ColumnBuilder { 52 | private float width; 53 | private Settings settings = Settings.builder().build(); 54 | 55 | ColumnBuilder() { 56 | } 57 | 58 | public ColumnBuilder width(float width) { 59 | this.width = width; 60 | return this; 61 | } 62 | 63 | private ColumnBuilder settings(Settings settings) { 64 | this.settings = settings; 65 | return this; 66 | } 67 | 68 | public ColumnBuilder font(final PDFont font) { 69 | settings.setFont(font); 70 | return this; 71 | } 72 | 73 | // Convenience setter for being halfway backwards compatible 74 | public ColumnBuilder font(Standard14Fonts.FontName fontName) { 75 | settings.setFont(new PDType1Font(fontName)); 76 | return this; 77 | } 78 | 79 | public ColumnBuilder fontSize(final Integer fontSize) { 80 | settings.setFontSize(fontSize); 81 | return this; 82 | } 83 | 84 | public ColumnBuilder textColor(final Color textColor) { 85 | settings.setTextColor(textColor); 86 | return this; 87 | } 88 | 89 | public ColumnBuilder backgroundColor(final Color backgroundColor) { 90 | settings.setBackgroundColor(backgroundColor); 91 | return this; 92 | } 93 | 94 | public ColumnBuilder padding(final float padding) { 95 | settings.setPaddingTop(padding); 96 | settings.setPaddingBottom(padding); 97 | settings.setPaddingLeft(padding); 98 | settings.setPaddingRight(padding); 99 | return this; 100 | } 101 | 102 | public ColumnBuilder borderWidth(final float borderWidth) { 103 | settings.setBorderWidthLeft(borderWidth); 104 | settings.setBorderWidthRight(borderWidth); 105 | settings.setBorderWidthTop(borderWidth); 106 | settings.setBorderWidthBottom(borderWidth); 107 | return this; 108 | } 109 | 110 | public ColumnBuilder borderWidthLeft(final float borderWidth) { 111 | settings.setBorderWidthLeft(borderWidth); 112 | return this; 113 | } 114 | 115 | public ColumnBuilder borderWidthRight(final float borderWidth) { 116 | settings.setBorderWidthRight(borderWidth); 117 | return this; 118 | } 119 | 120 | public ColumnBuilder borderStyleLeft(final BorderStyleInterface borderStyle) { 121 | settings.setBorderStyleLeft(borderStyle); 122 | return this; 123 | } 124 | 125 | public ColumnBuilder borderStyleRight(final BorderStyleInterface borderStyle) { 126 | settings.setBorderStyleRight(borderStyle); 127 | return this; 128 | } 129 | 130 | public ColumnBuilder borderColorLeft(final Color borderColor) { 131 | settings.setBorderColorLeft(borderColor); 132 | return this; 133 | } 134 | 135 | public ColumnBuilder borderColorRight(final Color borderColor) { 136 | settings.setBorderColorRight(borderColor); 137 | return this; 138 | } 139 | 140 | public ColumnBuilder horizontalAlignment(HorizontalAlignment alignment) { 141 | settings.setHorizontalAlignment(alignment); 142 | return this; 143 | } 144 | 145 | public ColumnBuilder verticalAlignment(VerticalAlignment alignment) { 146 | settings.setVerticalAlignment(alignment); 147 | return this; 148 | } 149 | 150 | public ColumnBuilder wordBreak(Boolean wordBreak) { 151 | settings.setWordBreak(wordBreak); 152 | return this; 153 | } 154 | 155 | public Column build() { 156 | return new Column(width, settings); 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/cell/VerticalTextCellDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing.cell; 2 | 3 | import lombok.NoArgsConstructor; 4 | import lombok.SneakyThrows; 5 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 6 | import org.apache.pdfbox.pdmodel.font.PDFont; 7 | import org.apache.pdfbox.util.Matrix; 8 | import org.vandeseer.easytable.drawing.DrawingContext; 9 | import org.vandeseer.easytable.settings.VerticalAlignment; 10 | import org.vandeseer.easytable.structure.cell.VerticalTextCell; 11 | import org.vandeseer.easytable.util.PdfUtil; 12 | 13 | import java.awt.*; 14 | import java.awt.geom.AffineTransform; 15 | import java.io.IOException; 16 | import java.util.Collections; 17 | import java.util.List; 18 | 19 | import static org.vandeseer.easytable.settings.HorizontalAlignment.CENTER; 20 | import static org.vandeseer.easytable.settings.HorizontalAlignment.RIGHT; 21 | 22 | /** 23 | * Allows vertical text drawing. Note that this class is still not fully 24 | * developed, e.g. there is no support for text alignment settings yet. 25 | */ 26 | @NoArgsConstructor 27 | public class VerticalTextCellDrawer extends AbstractCellDrawer { 28 | 29 | public VerticalTextCellDrawer(VerticalTextCell cell) { 30 | this.cell = cell; 31 | } 32 | 33 | /** 34 | * Does not yet support the settings of alignments. 35 | * 36 | * @param drawingContext 37 | */ 38 | @Override 39 | @SneakyThrows 40 | public void drawContent(DrawingContext drawingContext) { 41 | final float startX = drawingContext.getStartingPoint().x; 42 | final float startY = drawingContext.getStartingPoint().y; 43 | 44 | final PDFont currentFont = cell.getFont(); 45 | final int currentFontSize = cell.getFontSize(); 46 | final Color currentTextColor = cell.getTextColor(); 47 | 48 | float yOffset = startY + cell.getPaddingBottom(); 49 | 50 | float height = cell.getRow().getHeight(); 51 | 52 | if (cell.getRowSpan() > 1) { 53 | float rowSpanAdaption = cell.calculateHeightForRowSpan() - cell.getRow().getHeight(); 54 | yOffset -= rowSpanAdaption; 55 | height = cell.calculateHeightForRowSpan(); 56 | } 57 | 58 | final List lines = cell.isWordBreak() 59 | ? PdfUtil.getOptimalTextBreakLines(cell.getText(), currentFont, currentFontSize, (height - cell.getVerticalPadding())) 60 | : Collections.singletonList(cell.getText()); 61 | 62 | float textHeight = 0; 63 | for (String line : lines) { 64 | float currentHeight = PdfUtil.getStringWidth(line, currentFont, currentFontSize); 65 | textHeight = currentHeight > textHeight ? currentHeight : textHeight; 66 | } 67 | if (cell.isVerticallyAligned(VerticalAlignment.MIDDLE)) { 68 | yOffset += (height - textHeight - cell.getPaddingTop() - cell.getPaddingBottom()) / 2; 69 | } else if (cell.isVerticallyAligned(VerticalAlignment.TOP)) { 70 | yOffset += (height - textHeight - cell.getPaddingTop() - cell.getPaddingBottom()); 71 | } 72 | 73 | float xOffset = startX + cell.getPaddingLeft() - PdfUtil.getFontHeight(currentFont, currentFontSize); 74 | 75 | float textWidth = (PdfUtil.getFontHeight(currentFont, currentFontSize) // font height 76 | + PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing()) * lines.size(); // line spacing; 77 | 78 | if (cell.isHorizontallyAligned(CENTER)) { 79 | xOffset = xOffset + ((cell.getWidth() - cell.getPaddingRight() - cell.getPaddingLeft()) / 2 - textWidth / 2); 80 | } else if (cell.isHorizontallyAligned(RIGHT)) { 81 | xOffset = xOffset + cell.getWidth() - cell.getPaddingRight() - cell.getPaddingLeft() - textWidth; 82 | } 83 | 84 | for (int i = 0; i < lines.size(); i++) { 85 | String line = lines.get(i); 86 | 87 | xOffset += ( 88 | PdfUtil.getFontHeight(currentFont, currentFontSize) // font height 89 | + (i > 0 ? PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing() : 0f) // line spacing 90 | ); 91 | 92 | drawText(line, currentFont, currentFontSize, currentTextColor, xOffset, yOffset, drawingContext.getContentStream()); 93 | } 94 | } 95 | 96 | // TODO this is currently not used! 97 | @Override 98 | protected float calculateInnerHeight() { 99 | return 0; 100 | } 101 | 102 | 103 | protected void drawText(String text, PDFont font, int fontSize, Color color, float x, float y, PDPageContentStream contentStream) throws IOException { 104 | // Rotate by 90 degrees counter clockwise 105 | final AffineTransform transform = AffineTransform.getTranslateInstance(x, y); 106 | transform.concatenate(AffineTransform.getRotateInstance(Math.PI * 0.5)); 107 | transform.concatenate(AffineTransform.getTranslateInstance(-x, -y - fontSize)); 108 | 109 | contentStream.moveTo(x, y); 110 | contentStream.beginText(); 111 | 112 | // Do the transformation :) 113 | contentStream.setTextMatrix(new Matrix(transform)); 114 | 115 | contentStream.setNonStrokingColor(color); 116 | contentStream.setFont(font, fontSize); 117 | contentStream.newLineAtOffset(x, y); 118 | contentStream.showText(text); 119 | contentStream.endText(); 120 | contentStream.setCharacterSpacing(0); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/OverflowOnSamePageRepeatableHeaderTableDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable; 2 | 3 | import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND; 4 | 5 | import java.awt.geom.Point2D; 6 | import java.io.IOException; 7 | import java.util.Queue; 8 | import java.util.function.Supplier; 9 | 10 | import org.apache.pdfbox.pdmodel.PDDocument; 11 | import org.apache.pdfbox.pdmodel.PDPage; 12 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 13 | import org.vandeseer.easytable.TableDrawer.PageData; 14 | import org.vandeseer.easytable.structure.Row; 15 | 16 | import lombok.AccessLevel; 17 | import lombok.Builder; 18 | import lombok.Getter; 19 | import lombok.Setter; 20 | import lombok.experimental.SuperBuilder; 21 | 22 | @SuperBuilder(toBuilder = true) 23 | public class OverflowOnSamePageRepeatableHeaderTableDrawer extends TableDrawer { 24 | 25 | @Builder.Default 26 | private final int lanesPerPage = 2; 27 | 28 | @Builder.Default 29 | private final float spaceInBetween = 10f; 30 | 31 | @Builder.Default 32 | private int numberOfRowsToRepeat = 1; 33 | 34 | private Float headerHeight; 35 | 36 | // This is really meant as a private field. 37 | // Unfortunately it might be settable by the builder and we can't make it final :/ 38 | @Getter(AccessLevel.NONE) 39 | @Setter(AccessLevel.NONE) 40 | @Builder.Default 41 | private int actualTableLane = 1; 42 | 43 | @Override 44 | protected void drawPage(PageData pageData) { 45 | if (pageData.firstRowOnPage != 0) { 46 | drawHeaderForCurrentLane(); 47 | } 48 | drawerList.forEach(drawer -> 49 | drawWithFunction(pageData, new Point2D.Float(this.startX + calculateXOffset(), this.startY ), drawer) 50 | ); 51 | } 52 | 53 | private float calculateXOffset() { 54 | final float widthOfTableLanes = (actualTableLane - 1) * table.getWidth(); 55 | final float spacing = 56 | actualTableLane > 1 57 | ? (actualTableLane - 1) * spaceInBetween 58 | : 0; 59 | 60 | return widthOfTableLanes + spacing; 61 | } 62 | 63 | private float calculateHeightForFirstRows() { 64 | if (headerHeight != null) { 65 | return headerHeight; 66 | } 67 | 68 | float height = 0; 69 | for (int i = 0; i < numberOfRowsToRepeat; i++) { 70 | height += table.getRows().get(i).getHeight(); 71 | } 72 | 73 | // Cache and return 74 | headerHeight = height; 75 | return height; 76 | } 77 | 78 | @Override 79 | protected void determinePageToStartTable(float yOffsetOnNewPage) { 80 | float minimumRowsToFitHeight = 0; 81 | int minimumRowsToFit = table.getRows().size() > numberOfRowsToRepeat 82 | ? numberOfRowsToRepeat + 1 83 | : numberOfRowsToRepeat; 84 | 85 | for (final Row row : table.getRows().subList(0, minimumRowsToFit)) 86 | minimumRowsToFitHeight += row.getHeight(); 87 | 88 | if (startY - minimumRowsToFitHeight < endY) { 89 | startY = yOffsetOnNewPage + calculateHeightForFirstRows(); 90 | startTableInNewPage = true; 91 | } 92 | } 93 | 94 | public void drawHeaderForCurrentLane() { 95 | float adaption = 0; 96 | for (int k = 0; k < numberOfRowsToRepeat; k++) { 97 | adaption += table.getRows().get(k).getHeight(); 98 | Point2D.Float startPoint = new Point2D.Float(this.startX + calculateXOffset(), 99 | this.startY + calculateHeightForFirstRows() - adaption); 100 | drawRow(startPoint, table.getRows().get(k), k, (drawer, drawingContext) -> { 101 | drawer.drawBackground(drawingContext); 102 | drawer.drawContent(drawingContext); 103 | drawer.drawBorders(drawingContext); 104 | }); 105 | } 106 | } 107 | 108 | public void draw(Supplier documentSupplier, Supplier pageSupplier, float yOffset) throws IOException { 109 | final PDDocument document = documentSupplier.get(); 110 | 111 | // We create one throwaway page to be able to calculate the page data upfront 112 | float startOnNewPage = pageSupplier.get().getMediaBox().getHeight() - yOffset; 113 | determinePageToStartTable(startOnNewPage); 114 | final Queue pageDataQueue = computeRowsOnPagesWithNewPageStartOf(startOnNewPage); 115 | 116 | for (int i = 0; !pageDataQueue.isEmpty(); i++) { 117 | final PDPage pageToDrawOn = determinePageToDraw(i, document, pageSupplier); 118 | 119 | if ((i == 0 && startTableInNewPage) || i > 0 || document.getNumberOfPages() == 0 || actualTableLane != lanesPerPage) { 120 | startTableInNewPage = false; 121 | } 122 | 123 | if (i == 0) { 124 | tableStartPage = pageToDrawOn; 125 | } 126 | 127 | try (final PDPageContentStream newPageContentStream = new PDPageContentStream(document, pageToDrawOn, APPEND, compress)) { 128 | for (int j = 1; j <= lanesPerPage && !pageDataQueue.isEmpty(); j++) { 129 | actualTableLane = j; 130 | if (actualTableLane > 1) { 131 | this.startY = startOnNewPage; 132 | } 133 | 134 | this.contentStream(newPageContentStream) 135 | .page(pageToDrawOn) 136 | .drawPage(pageDataQueue.poll()); 137 | } 138 | } 139 | startY(pageToDrawOn.getMediaBox().getHeight() - yOffset); 140 | } 141 | } 142 | 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/structure/Row.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.apache.pdfbox.pdmodel.font.PDFont; 8 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 9 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 10 | import org.vandeseer.easytable.settings.BorderStyleInterface; 11 | import org.vandeseer.easytable.settings.HorizontalAlignment; 12 | import org.vandeseer.easytable.settings.Settings; 13 | import org.vandeseer.easytable.settings.VerticalAlignment; 14 | import org.vandeseer.easytable.structure.cell.AbstractCell; 15 | 16 | import java.awt.*; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import static java.util.Comparator.naturalOrder; 21 | 22 | @Builder 23 | @Getter 24 | @Setter(AccessLevel.PACKAGE) 25 | public class Row { 26 | 27 | private static final Float DEFAULT_HEIGHT = 10f; 28 | 29 | private Table table; 30 | 31 | private List cells; 32 | 33 | @Setter(AccessLevel.NONE) 34 | private Settings settings; 35 | 36 | @Setter(AccessLevel.PACKAGE) 37 | private Float height; 38 | 39 | private Row next; 40 | 41 | 42 | private Row(final List cells) { 43 | super(); 44 | this.cells = cells; 45 | } 46 | 47 | public float getHeight() { 48 | if (table == null) { 49 | throw new TableNotYetBuiltException(); 50 | } 51 | 52 | if (height == null) { 53 | this.height = getCells().stream() 54 | .filter(cell -> cell.getRowSpan() == 1) 55 | .map(AbstractCell::getHeight) 56 | .max(naturalOrder()) 57 | .orElse(DEFAULT_HEIGHT); 58 | } 59 | 60 | return height; 61 | } 62 | 63 | void doRowSpanSizeAdaption(float heightOfHighestCell, float rowsHeight) { 64 | final float rowSpanSizeDifference = heightOfHighestCell - rowsHeight; 65 | this.height += (this.height / (heightOfHighestCell - rowSpanSizeDifference)) * rowSpanSizeDifference; 66 | } 67 | 68 | public Color getBorderColor() { 69 | // We could simply return any of the four border color values ... 70 | return settings.getBorderColorBottom(); 71 | } 72 | 73 | public static class RowBuilder { 74 | 75 | private List cells = new ArrayList<>(); 76 | private Settings settings = Settings.builder().build(); 77 | 78 | private RowBuilder() { 79 | 80 | } 81 | 82 | public RowBuilder add(final AbstractCell cell) { 83 | cells.add(cell); 84 | return this; 85 | } 86 | 87 | public Row.RowBuilder font(final PDFont font) { 88 | settings.setFont(font); 89 | return this; 90 | } 91 | 92 | // Convenience setter for being halfway backwards compatible 93 | public Row.RowBuilder font(Standard14Fonts.FontName fontName) { 94 | settings.setFont(new PDType1Font(fontName)); 95 | return this; 96 | } 97 | 98 | public Row.RowBuilder fontSize(final Integer fontSize) { 99 | settings.setFontSize(fontSize); 100 | return this; 101 | } 102 | 103 | public Row.RowBuilder textColor(final Color textColor) { 104 | settings.setTextColor(textColor); 105 | return this; 106 | } 107 | 108 | public Row.RowBuilder backgroundColor(final Color backgroundColor) { 109 | settings.setBackgroundColor(backgroundColor); 110 | return this; 111 | } 112 | 113 | public Row.RowBuilder padding(final float padding) { 114 | settings.setPaddingTop(padding); 115 | settings.setPaddingBottom(padding); 116 | settings.setPaddingLeft(padding); 117 | settings.setPaddingRight(padding); 118 | return this; 119 | } 120 | 121 | public Row.RowBuilder borderWidth(final float borderWidth) { 122 | settings.setBorderWidthTop(borderWidth); 123 | settings.setBorderWidthBottom(borderWidth); 124 | settings.setBorderWidthLeft(borderWidth); 125 | settings.setBorderWidthRight(borderWidth); 126 | return this; 127 | } 128 | 129 | public Row.RowBuilder borderStyle(final BorderStyleInterface borderStyle) { 130 | settings.setBorderStyleTop(borderStyle); 131 | settings.setBorderStyleBottom(borderStyle); 132 | settings.setBorderStyleLeft(borderStyle); 133 | settings.setBorderStyleRight(borderStyle); 134 | return this; 135 | } 136 | 137 | public Row.RowBuilder borderColor(final Color borderColor) { 138 | settings.setBorderColorBottom(borderColor); 139 | settings.setBorderColorTop(borderColor); 140 | settings.setBorderColorLeft(borderColor); 141 | settings.setBorderColorRight(borderColor); 142 | return this; 143 | } 144 | 145 | public Row.RowBuilder horizontalAlignment(HorizontalAlignment alignment) { 146 | settings.setHorizontalAlignment(alignment); 147 | return this; 148 | } 149 | 150 | public Row.RowBuilder verticalAlignment(VerticalAlignment alignment) { 151 | settings.setVerticalAlignment(alignment); 152 | return this; 153 | } 154 | 155 | public Row.RowBuilder wordBreak(Boolean wordBreak) { 156 | settings.setWordBreak(wordBreak); 157 | return this; 158 | } 159 | 160 | public Row build() { 161 | final Row row = new Row(cells); 162 | row.settings = settings; 163 | row.height = height; 164 | 165 | return row; 166 | } 167 | 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/settings/Settings.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.settings; 2 | 3 | import lombok.*; 4 | import org.apache.pdfbox.pdmodel.font.PDFont; 5 | 6 | import java.awt.*; 7 | 8 | @Data 9 | @Builder 10 | public class Settings { 11 | 12 | private PDFont font; 13 | private Integer fontSize; 14 | 15 | private Color textColor; 16 | private Color backgroundColor; 17 | 18 | private Float borderWidthTop; 19 | private Float borderWidthBottom; 20 | private Float borderWidthLeft; 21 | private Float borderWidthRight; 22 | 23 | private Color borderColorTop; 24 | private Color borderColorBottom; 25 | private Color borderColorLeft; 26 | private Color borderColorRight; 27 | 28 | private Float paddingTop; 29 | private Float paddingBottom; 30 | private Float paddingLeft; 31 | private Float paddingRight; 32 | 33 | private BorderStyleInterface borderStyleTop; 34 | private BorderStyleInterface borderStyleBottom; 35 | private BorderStyleInterface borderStyleLeft; 36 | private BorderStyleInterface borderStyleRight; 37 | 38 | private HorizontalAlignment horizontalAlignment; 39 | private VerticalAlignment verticalAlignment; 40 | 41 | // We use a boxed Boolean internally in order to be able to model the absence of a value. 42 | // For callers outside it should expose only the primitive though. 43 | @Getter(AccessLevel.PACKAGE) 44 | @Setter(AccessLevel.NONE) 45 | private Boolean wordBreak; 46 | 47 | public boolean isWordBreak() { 48 | return wordBreak != null && wordBreak; 49 | } 50 | 51 | public void setWordBreak(boolean wordBreak) { 52 | this.wordBreak = wordBreak; 53 | } 54 | 55 | public void fillingMergeBy(Settings settings) { 56 | fillingMergeFontSettings(settings); 57 | fillingMergePaddingSettings(settings); 58 | fillingMergeBorderWidthSettings(settings); 59 | fillingMergeBorderStyleSettings(settings); 60 | fillingMergeColorSettings(settings); 61 | fillingMergeAlignmentSettings(settings); 62 | fillingMergeWordBreakSetting(settings); 63 | } 64 | 65 | private void fillingMergeWordBreakSetting(Settings settings) { 66 | // Note that we use the boxed Boolean only here internally! 67 | if (wordBreak == null && settings.wordBreak != null) { 68 | wordBreak = settings.getWordBreak(); 69 | } 70 | } 71 | 72 | private void fillingMergePaddingSettings(Settings settings) { 73 | if (getPaddingBottom() == null && settings.getPaddingBottom() != null) { 74 | paddingBottom = settings.getPaddingBottom(); 75 | } 76 | 77 | if (getPaddingTop() == null && settings.getPaddingTop() != null) { 78 | paddingTop = settings.getPaddingTop(); 79 | } 80 | 81 | if (getPaddingLeft() == null && settings.getPaddingLeft() != null) { 82 | paddingLeft = settings.getPaddingLeft(); 83 | } 84 | 85 | if (getPaddingRight() == null && settings.getPaddingRight() != null) { 86 | paddingRight = settings.getPaddingRight(); 87 | } 88 | } 89 | 90 | private void fillingMergeBorderWidthSettings(Settings settings) { 91 | if (getBorderWidthBottom() == null && settings.getBorderWidthBottom() != null) { 92 | borderWidthBottom = settings.getBorderWidthBottom(); 93 | } 94 | 95 | if (getBorderWidthTop() == null && settings.getBorderWidthTop() != null) { 96 | borderWidthTop = settings.getBorderWidthTop(); 97 | } 98 | 99 | if (getBorderWidthLeft() == null && settings.getBorderWidthLeft() != null) { 100 | borderWidthLeft = settings.getBorderWidthLeft(); 101 | } 102 | 103 | if (getBorderWidthRight() == null && settings.getBorderWidthRight() != null) { 104 | borderWidthRight = settings.getBorderWidthRight(); 105 | } 106 | } 107 | 108 | private void fillingMergeBorderStyleSettings(Settings settings) { 109 | if (getBorderStyleBottom() == null && settings.getBorderStyleBottom() != null) { 110 | borderStyleBottom = settings.getBorderStyleBottom(); 111 | } 112 | 113 | if (getBorderStyleTop() == null && settings.getBorderStyleTop() != null) { 114 | borderStyleTop = settings.getBorderStyleTop(); 115 | } 116 | 117 | if (getBorderStyleLeft() == null && settings.getBorderStyleLeft() != null) { 118 | borderStyleLeft = settings.getBorderStyleLeft(); 119 | } 120 | 121 | if (getBorderStyleRight() == null && settings.getBorderStyleRight() != null) { 122 | borderStyleRight = settings.getBorderStyleRight(); 123 | } 124 | } 125 | 126 | private void fillingMergeColorSettings(Settings settings) { 127 | if (getTextColor() == null && settings.getTextColor() != null) { 128 | textColor = settings.getTextColor(); 129 | } 130 | 131 | if (getBackgroundColor() == null && settings.getBackgroundColor() != null) { 132 | backgroundColor = settings.getBackgroundColor(); 133 | } 134 | 135 | if (getBorderColorTop() == null && settings.getBorderColorTop() != null) { 136 | borderColorTop = settings.getBorderColorTop(); 137 | } 138 | 139 | if (getBorderColorBottom() == null && settings.getBorderColorBottom() != null) { 140 | borderColorBottom = settings.getBorderColorBottom(); 141 | } 142 | 143 | if (getBorderColorLeft() == null && settings.getBorderColorLeft() != null) { 144 | borderColorLeft = settings.getBorderColorLeft(); 145 | } 146 | 147 | if (getBorderColorRight() == null && settings.getBorderColorRight() != null) { 148 | borderColorRight = settings.getBorderColorRight(); 149 | } 150 | } 151 | 152 | private void fillingMergeAlignmentSettings(Settings settings) { 153 | if (getHorizontalAlignment() == null && settings.getHorizontalAlignment() != null) { 154 | horizontalAlignment = settings.getHorizontalAlignment(); 155 | } 156 | 157 | if (getVerticalAlignment() == null && settings.getVerticalAlignment() != null) { 158 | verticalAlignment = settings.getVerticalAlignment(); 159 | } 160 | } 161 | 162 | private void fillingMergeFontSettings(Settings settings) { 163 | if (getFont() == null && settings.getFont() != null) { 164 | font = settings.getFont(); 165 | } 166 | 167 | if (getFontSize() == null && settings.getFontSize() != null) { 168 | fontSize = settings.getFontSize(); 169 | } 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/settings/SettingsTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.settings; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 6 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 7 | import org.junit.Test; 8 | import org.vandeseer.TestUtils; 9 | import org.vandeseer.easytable.settings.VerticalAlignment; 10 | import org.vandeseer.easytable.structure.Row; 11 | import org.vandeseer.easytable.structure.Table; 12 | import org.vandeseer.easytable.structure.cell.TextCell; 13 | 14 | import java.awt.*; 15 | import java.io.IOException; 16 | 17 | import static junit.framework.TestCase.assertTrue; 18 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.*; 19 | import static org.vandeseer.TestUtils.getActualPdfFor; 20 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 21 | import static org.vandeseer.easytable.settings.HorizontalAlignment.*; 22 | import static org.vandeseer.easytable.settings.VerticalAlignment.BOTTOM; 23 | import static org.vandeseer.easytable.settings.VerticalAlignment.TOP; 24 | 25 | // TODO test border color on row level 26 | // TODO test the precedence of cell level settings of row level/table level settings 27 | public class SettingsTest { 28 | 29 | private final static Color PURPLE_LIGHT_1 = new Color(206, 186, 230); 30 | private final static Color PURPLE_LIGHT_2 = new Color(230, 218, 242); 31 | 32 | private static final String FILE_NAME = "differentFontsInCells.pdf"; 33 | 34 | @Test 35 | public void differentSettings() throws IOException { 36 | final Table.TableBuilder tableBuilder = Table.builder() 37 | .addColumnsOfWidth(170, 170, 150) 38 | .fontSize(8) 39 | .font(new PDType1Font(HELVETICA)) 40 | .wordBreak(true); 41 | 42 | tableBuilder.addRow(Row.builder() 43 | .add(TextCell.builder().text("This is top right aligned without a border." + 44 | "It has a tiny font and a pretty big padding") 45 | .horizontalAlignment(RIGHT) 46 | .verticalAlignment(TOP) 47 | .fontSize(5) 48 | .padding(20) 49 | .build()) 50 | .add(TextCell.builder().text("Another cell with a" + 51 | " useless story, because I am to lazy to get a lorem-ipsum. It is bottom aligned.") 52 | .lineSpacing(1.8f) 53 | .padding(30) 54 | .verticalAlignment(BOTTOM) 55 | .backgroundColor(PURPLE_LIGHT_1) 56 | .build()) 57 | .add(TextCell.builder().text("This is center and middle aligned with a line spacing of 1.1") 58 | .verticalAlignment(VerticalAlignment.MIDDLE) 59 | .horizontalAlignment(CENTER) 60 | .lineSpacing(1.1f) 61 | .font(new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN)) 62 | .build()) 63 | .backgroundColor(PURPLE_LIGHT_2) 64 | .height(200f) 65 | .build()); 66 | 67 | tableBuilder.addRow(Row.builder() 68 | .add(TextCell.builder().text("This is left bottom aligned.") 69 | .horizontalAlignment(LEFT) 70 | .verticalAlignment(BOTTOM) 71 | .textColor(Color.BLACK) 72 | .font(new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE)) 73 | .fontSize(16) 74 | .padding(30) 75 | .build()) 76 | .add(TextCell.builder().text("Bavaria ipsum dolor sit amet Schaung kost nix des Biagadn obandeln. " + 77 | "Di gscheit des is hoid aso kummd Haberertanz heitzdog de Sonn des is a gmahde Wiesn, " + 78 | "Jo mei Kuaschwanz wia ned woar pfenningguat. Wos griasd eich midnand hi om auf’n Gipfe des " + 79 | "wiad a Mordsgaudi lem und lem lossn Weibaleid obacht mei ebba, in da. " + 80 | "liab Griasnoggalsubbm wea ko, dea ko hob mei.") 81 | .padding(20) 82 | .backgroundColor(Color.DARK_GRAY) 83 | .horizontalAlignment(JUSTIFY) 84 | .textColor(Color.WHITE) 85 | .lineSpacing(0.9f) 86 | .font(new PDType1Font(COURIER_BOLD)) 87 | .build()) 88 | .add(TextCell.builder().text("This is bottom right aligned and does have a padding of 10, " + 89 | "but a line spacing of 0.6") 90 | .verticalAlignment(BOTTOM) 91 | .horizontalAlignment(RIGHT) 92 | .padding(10) 93 | .lineSpacing(0.6f) 94 | .build()) 95 | .backgroundColor(Color.LIGHT_GRAY) 96 | .build()); 97 | 98 | tableBuilder.addRow(Row.builder() 99 | .add(TextCell.builder().text("Fubar! Top right!") 100 | .horizontalAlignment(RIGHT) 101 | .verticalAlignment(TOP) 102 | .textColor(Color.BLACK) 103 | .font(new PDType1Font(TIMES_ITALIC)) 104 | .fontSize(14) 105 | .padding(8) 106 | .build()) 107 | .add(TextCell.builder().text("Here\nwe\nhave\n\nseveral\nnew\nlines") 108 | .padding(20) 109 | .backgroundColor(PURPLE_LIGHT_1) 110 | .lineSpacing(0.9f) 111 | .font(new PDType1Font(COURIER_BOLD)) 112 | .build()) 113 | .add(TextCell.builder().text("Bottom. Center.") 114 | .verticalAlignment(BOTTOM) 115 | .horizontalAlignment(CENTER) 116 | .padding(14) 117 | .lineSpacing(0.6f) 118 | .build()) 119 | .backgroundColor(PURPLE_LIGHT_2) 120 | .build()); 121 | 122 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME, tableBuilder.build()); 123 | 124 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 125 | assertTrue(compareResult.isEqual()); 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/easytable/structure/cell/TextCellTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.structure.cell; 2 | 3 | import org.apache.pdfbox.pdmodel.font.PDFont; 4 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import org.junit.rules.ExpectedException; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | import org.vandeseer.easytable.structure.Column; 12 | import org.vandeseer.easytable.structure.Row; 13 | import org.vandeseer.easytable.structure.Table; 14 | import org.vandeseer.easytable.structure.TableNotYetBuiltException; 15 | import org.vandeseer.easytable.util.PdfUtil; 16 | 17 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 18 | import static org.hamcrest.MatcherAssert.assertThat; 19 | import static org.hamcrest.core.IsEqual.equalTo; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.mockito.Mockito.mock; 22 | import static org.mockito.Mockito.when; 23 | 24 | public class TextCellTest { 25 | 26 | @Mock 27 | private Row row; 28 | 29 | @Mock 30 | private Column column; 31 | 32 | @Mock 33 | private Table table; 34 | 35 | private final PDFont font = new PDType1Font(HELVETICA); 36 | 37 | private final int fontSize = 10; 38 | 39 | @Rule 40 | public final ExpectedException exception = ExpectedException.none(); 41 | 42 | @Before 43 | public void before() { 44 | MockitoAnnotations.initMocks(this); 45 | } 46 | 47 | @Test 48 | public void getWidth_regularCellWithNoWrappingText() { 49 | final String text = "abc"; 50 | 51 | final TextCell cell = prepareForTest( 52 | TextCell.builder() 53 | .font(font) 54 | .fontSize(fontSize) 55 | .text(text) 56 | .build() 57 | ); 58 | 59 | assertThat(cell.getWidthOfText(), equalTo(PdfUtil.getStringWidth(text, font, fontSize))); 60 | } 61 | 62 | @Test 63 | public void getWidth_cellWithWrappingText_columnSizeOk() { 64 | setColumnWidthTo(9f); // smaller than the size of the text "abc" 65 | 66 | final TextCell cell = prepareForTest( 67 | TextCell.builder() 68 | .font(font) 69 | .fontSize(fontSize) 70 | .text("abc abc abc abc") 71 | .paddingLeft(0) 72 | .paddingRight(0) 73 | .wordBreak(true) 74 | .build() 75 | ); 76 | 77 | assertThat(cell.getWidthOfText() + cell.getHorizontalPadding(), equalTo(PdfUtil.getStringWidth("a-", font, fontSize))); 78 | } 79 | 80 | @Test 81 | public void getWidth_cellWithWrappingText_columnTooSmall() { 82 | setColumnWidthTo(5f); 83 | 84 | final TextCell cell = prepareForTest( 85 | TextCell.builder() 86 | .font(font) 87 | .fontSize(fontSize) 88 | .text("a") 89 | .paddingLeft(0) 90 | .paddingRight(0) 91 | .wordBreak(true) 92 | .build() 93 | ); 94 | 95 | assertThat(cell.getWidthOfText() + cell.getHorizontalPadding(), equalTo(PdfUtil.getStringWidth("a", font, fontSize))); 96 | } 97 | 98 | @Test 99 | public void getWidth_cellWithSpanningNoWrapping() { 100 | prepareTwoSpanningColumnsOfSize(20f, 20f); 101 | 102 | final String text = "abc"; 103 | 104 | final TextCell cell = prepareForTest( 105 | TextCell.builder() 106 | .font(font) 107 | .fontSize(fontSize) 108 | .text(text) 109 | .colSpan(2) 110 | .build() 111 | ); 112 | 113 | assertThat(cell.getWidthOfText(), equalTo(PdfUtil.getStringWidth(text, font, fontSize))); 114 | } 115 | 116 | @Test 117 | public void getWidth_cellWithSpanningWithWrapping() { 118 | prepareTwoSpanningColumnsOfSize(35f, 15f); 119 | 120 | final String text = "abc abc abc abc abc abc abc abc abc abc"; 121 | final TextCell cell = prepareForTest( 122 | TextCell.builder() 123 | .text(text) 124 | .font(font) 125 | .fontSize(fontSize) 126 | .colSpan(2) 127 | .paddingLeft(5) 128 | .paddingRight(5) 129 | .wordBreak(true) 130 | .build() 131 | ); 132 | 133 | // The two columns will have a width of 35 + 15 = 50; the size of the text "abc abc" has 35.02 and the padding 134 | // is 10 in sum. Therefore the text will be split in pieces of "abc abc". 135 | assertThat(cell.getWidthOfText(), equalTo(PdfUtil.getStringWidth("abc abc", font, fontSize))); 136 | } 137 | 138 | @Test 139 | public void getCell_toBuilderFeature() { 140 | // Create two cells without border 141 | TextCell originalCell1 = TextCell.builder().text("11").paddingTop(35).paddingBottom(15).build(); 142 | TextCell originalCell2 = TextCell.builder().text("12").paddingTop(15).paddingBottom(25).build(); 143 | 144 | final Row row = Row.builder() 145 | .add(originalCell1.toBuilder().borderWidthBottom(1F).build()) // add the border 146 | .add(originalCell2.toBuilder().borderWidthBottom(1F).build()) // add the border 147 | .build(); 148 | 149 | for(AbstractCell cell : row.getCells()) { 150 | assertEquals(1F,cell.getBorderWidthBottom(),0F); // test if border exists 151 | } 152 | } 153 | 154 | @Test 155 | public void getHeight_shouldThrowExceptionIfTableNotYetBuilt() { 156 | AbstractCell cell = TextCell.builder().text("abc").paddingTop(35).paddingBottom(15).build(); 157 | 158 | exception.expect(TableNotYetBuiltException.class); 159 | cell.getHeight(); 160 | } 161 | 162 | private void prepareTwoSpanningColumnsOfSize(float sizeColumn1, float sizeColumn2) { 163 | when(column.getWidth()).thenReturn(sizeColumn1); 164 | Column nextColumn = mock(Column.class); 165 | when(nextColumn.getWidth()).thenReturn(sizeColumn2); 166 | when(column.getNext()).thenReturn(nextColumn); 167 | } 168 | 169 | private TextCell prepareForTest(TextCell cell) { 170 | when(row.getTable()).thenReturn(table); 171 | cell.setRow(row); 172 | cell.setColumn(column); 173 | return cell; 174 | } 175 | 176 | private void setColumnWidthTo(float width) { 177 | when(column.getWidth()).thenReturn(width); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/settings/SettingsOverridingBordersTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.settings; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 6 | import org.junit.Test; 7 | import org.vandeseer.TestUtils; 8 | import org.vandeseer.easytable.settings.BorderStyle; 9 | import org.vandeseer.easytable.structure.Row; 10 | import org.vandeseer.easytable.structure.Table; 11 | import org.vandeseer.easytable.structure.cell.TextCell; 12 | 13 | import java.awt.*; 14 | 15 | import static java.awt.Color.BLACK; 16 | import static java.awt.Color.GRAY; 17 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.*; 18 | import static org.junit.Assert.assertTrue; 19 | import static org.vandeseer.TestUtils.getActualPdfFor; 20 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 21 | import static org.vandeseer.easytable.settings.HorizontalAlignment.CENTER; 22 | import static org.vandeseer.easytable.settings.VerticalAlignment.MIDDLE; 23 | 24 | public class SettingsOverridingBordersTest { 25 | 26 | public static final String FILE_NAME = "settingsOverridingBorders.pdf"; 27 | 28 | @Test 29 | public void createDocumentWithTables() throws Exception { 30 | TestUtils.createAndSaveDocumentWithTables(FILE_NAME, 31 | createTableWithFontSettingAndBorderColorOverriding() 32 | ); 33 | 34 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 35 | assertTrue(compareResult.isEqual()); 36 | } 37 | 38 | private Table createTableWithFontSettingAndBorderColorOverriding() { 39 | final Table.TableBuilder tableBuilder = Table.builder() 40 | .addColumnsOfWidth(100, 100, 100) 41 | .padding(1f) 42 | .borderWidth(2) 43 | .borderStyle(BorderStyle.DOTTED) 44 | .horizontalAlignment(CENTER) 45 | .fontSize(16).font(new PDType1Font(HELVETICA)) 46 | .borderColor(Color.RED); 47 | 48 | tableBuilder.addRow( 49 | Row.builder() 50 | .add( 51 | TextCell.builder() 52 | .text("AbcDef") 53 | .colSpan(2) 54 | .borderWidth(1) 55 | .build() 56 | ) 57 | .add( 58 | TextCell.builder() 59 | .text("Hij klmn") 60 | .build() 61 | ) 62 | .borderWidth(3) 63 | .borderStyle(BorderStyle.SOLID) 64 | .font(new PDType1Font(COURIER_BOLD)).fontSize(12) 65 | .build() 66 | ); 67 | 68 | tableBuilder.addRow( 69 | Row.builder() 70 | .add(TextCell.builder().text("Puro").backgroundColor(Color.YELLOW).build()) 71 | .add(TextCell.builder().text("Boozoo").build()) 72 | .add(TextCell.builder().text("bazoo").build()) 73 | .borderWidth(2.4f) 74 | .borderStyle(BorderStyle.SOLID) 75 | .borderColor(BLACK) 76 | .font(new PDType1Font(COURIER_BOLD)) 77 | .fontSize(12) 78 | .build() 79 | ); 80 | 81 | tableBuilder.addRow( 82 | Row.builder() 83 | .add( 84 | TextCell.builder() 85 | .text("Pur") 86 | .borderWidth(0) 87 | .borderWidthRight(1) 88 | .borderWidthBottom(1) 89 | .backgroundColor(Color.YELLOW) 90 | .build() 91 | ) 92 | .add( 93 | TextCell.builder() 94 | .text("Heyho") 95 | .borderWidth(0) 96 | .borderWidthBottom(2) 97 | .build() 98 | ) 99 | .add( 100 | TextCell.builder() 101 | .text("BazBaz") 102 | .padding(22f) 103 | .borderWidthBottom(1.5f) 104 | .borderStyle(BorderStyle.SOLID) 105 | .build() 106 | ) 107 | .borderWidth(0.2f) 108 | .borderStyle(BorderStyle.DASHED) 109 | .borderColor(Color.BLUE) 110 | .font(new PDType1Font(COURIER_BOLD)).fontSize(12).build()); 111 | 112 | tableBuilder.addRow( 113 | Row.builder() 114 | .add(TextCell.builder().text("Pur").backgroundColor(Color.YELLOW).borderWidth(1).build()) 115 | .add( 116 | TextCell.builder() 117 | .text("Booz2") 118 | .borderWidth(0) 119 | .borderWidthBottom(4) 120 | .borderStyle(BorderStyle.SOLID) 121 | .padding(6) 122 | .build() 123 | ) 124 | .add( 125 | TextCell.builder() 126 | .text("bazBoo") 127 | .borderColor(GRAY) 128 | .borderWidth(0) 129 | .borderWidthBottom(1) 130 | .borderStyle(BorderStyle.DASHED) 131 | .font(new PDType1Font(HELVETICA_OBLIQUE)) 132 | .fontSize(5) 133 | .verticalAlignment(MIDDLE) 134 | .build() 135 | ) 136 | .build()); 137 | 138 | return tableBuilder.build(); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/regressiontest/RingManagerTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.regressiontest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 8 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 9 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.vandeseer.TestUtils; 13 | import org.vandeseer.easytable.TableDrawer; 14 | import org.vandeseer.easytable.structure.Row; 15 | import org.vandeseer.easytable.structure.Table; 16 | import org.vandeseer.easytable.structure.Table.TableBuilder; 17 | import org.vandeseer.easytable.structure.cell.TextCell; 18 | 19 | import java.awt.*; 20 | 21 | import static junit.framework.TestCase.assertTrue; 22 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 23 | import static org.vandeseer.TestUtils.getActualPdfFor; 24 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 25 | 26 | public class RingManagerTest { 27 | 28 | private static final String FILE_NAME = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" + "ringmanager.pdf"; 29 | 30 | @Before 31 | public void before() { 32 | TestUtils.assertRegressionFolderExists(); 33 | } 34 | 35 | @Test 36 | public void testRingManagerDocument() throws Exception { 37 | try (final PDDocument document = new PDDocument()) { 38 | final PDPage page = new PDPage(PDRectangle.A4); 39 | document.addPage(page); 40 | 41 | final float startY = page.getMediaBox().getHeight() - 150; 42 | final int startX = 56; 43 | 44 | try (final PDPageContentStream contentStream = new PDPageContentStream(document, page)) { 45 | final Table table = getRingManagerTable(); 46 | 47 | TableDrawer.builder() 48 | .contentStream(contentStream) 49 | .table(table) 50 | .startX(startX) 51 | .startY(startY) 52 | .build() 53 | .draw(); 54 | 55 | contentStream.setFont(new PDType1Font(HELVETICA), 8.0f); 56 | contentStream.beginText(); 57 | 58 | contentStream.newLineAtOffset(startX, startY - (table.getHeight() + 22)); 59 | contentStream.showText("Dieser Kampf muss der WB nicht entsprechen, da als Sparringskampf angesetzt."); 60 | contentStream.endText(); 61 | } 62 | 63 | document.save(TestUtils.TARGET_FOLDER + "/" + FILE_NAME); 64 | 65 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 66 | assertTrue(compareResult.isEqual()); 67 | } 68 | } 69 | 70 | private Table getRingManagerTable() { 71 | final TableBuilder tableBuilder = Table.builder() 72 | .addColumnsOfWidth(26, 70, 390) 73 | .fontSize(9) 74 | .borderColor(Color.GRAY) 75 | .font(new PDType1Font(HELVETICA)); 76 | 77 | final float borderWidthOuter = 1.5f; 78 | final float borderWidthInner = 1.0f; 79 | 80 | tableBuilder.addRow(Row.builder() 81 | .add( 82 | TextCell.builder().text("1.") 83 | .borderWidthTop(borderWidthOuter) 84 | .borderWidthLeft(borderWidthOuter) 85 | .borderWidthRight(borderWidthInner) 86 | .build()) 87 | .add( 88 | TextCell.builder().text("WK DBV(s)") 89 | .borderWidthTop(borderWidthOuter) 90 | .borderWidthRight(borderWidthInner) 91 | .borderWidthLeft(borderWidthInner) 92 | .build()) 93 | .add( 94 | TextCell.builder().text("Rote Ecke:") 95 | .borderWidthTop(borderWidthOuter) 96 | .borderWidthRight(borderWidthOuter) 97 | .build() 98 | ).build()); 99 | 100 | tableBuilder.addRow(Row.builder() 101 | .add( 102 | TextCell.builder() 103 | .text("") 104 | .borderWidthLeft(borderWidthOuter) 105 | .build()) 106 | .add( 107 | TextCell.builder().text("Jugend") 108 | .borderWidthRight(borderWidthInner) 109 | .borderWidthLeft(borderWidthInner) 110 | .build()) 111 | .add( 112 | TextCell.builder().text("Thomas Test, m, FC St. Pauli, 01.01.1998, Jugend, 67,5 kg, 12K (8S, 4N, 0U)") 113 | .borderWidthBottom(borderWidthInner) 114 | .borderWidthRight(borderWidthOuter) 115 | .build()) 116 | .build()); 117 | 118 | tableBuilder.addRow(Row.builder().add( 119 | TextCell.builder().text("").borderWidthLeft(borderWidthOuter).build()).add( 120 | TextCell.builder().text("3x3") 121 | .borderWidthRight(borderWidthInner) 122 | .borderWidthLeft(borderWidthInner) 123 | .build()).add( 124 | TextCell.builder().text("Blaue Ecke:").borderWidthRight(borderWidthOuter).build()).build()); 125 | 126 | tableBuilder.addRow(Row.builder().add( 127 | TextCell.builder().text("") 128 | .borderWidthLeft(borderWidthOuter) 129 | .borderWidthBottom(borderWidthOuter) 130 | .build()).add( 131 | TextCell.builder().text("10 Uz, KS") 132 | .borderWidthRight(borderWidthInner) 133 | .borderWidthLeft(borderWidthInner) 134 | .borderWidthBottom(borderWidthOuter) 135 | .build()).add( 136 | TextCell.builder().text("Bernd Beispiel, m, Wedeler TSV, 02.01.1999, Jugend, 68,2 kg, 9K (7S, 2N, 0U)") 137 | .borderWidthBottom(borderWidthOuter) 138 | .borderWidthRight(borderWidthOuter) 139 | .build()).build()); 140 | 141 | return tableBuilder.build(); 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/RowSpanningSizingTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.junit.Test; 6 | import org.vandeseer.TestUtils; 7 | import org.vandeseer.easytable.structure.Row; 8 | import org.vandeseer.easytable.structure.Table; 9 | import org.vandeseer.easytable.structure.cell.TextCell; 10 | 11 | import static junit.framework.TestCase.assertTrue; 12 | import static org.vandeseer.TestUtils.getActualPdfFor; 13 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 14 | 15 | public class RowSpanningSizingTest { 16 | 17 | private static final String FILE_NAME = "rowSpanningSizing.pdf"; 18 | 19 | @Test 20 | public void createDocumentWithTables() throws Exception { 21 | TestUtils.createAndSaveDocumentWithTables(FILE_NAME, 22 | createRegularTable(), 23 | createComplexTable1(), 24 | createComplexTable2(), 25 | createComplexTable3() 26 | ); 27 | 28 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 29 | assertTrue(compareResult.isEqual()); 30 | } 31 | 32 | private Table createRegularTable() { 33 | return Table.builder() 34 | .addColumnsOfWidth(50, 50) 35 | .addRow(Row.builder() 36 | .add(TextCell.builder().borderWidth(1).text("1").build()) 37 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds").rowSpan(2).build()) 38 | .build()) 39 | .addRow(Row.builder() 40 | .add(TextCell.builder().borderWidth(1).text("2\n2").build()) 41 | .build()) 42 | .addRow(Row.builder() 43 | .add(TextCell.builder().borderWidth(1).text("fu").build()) 44 | .add(TextCell.builder().borderWidth(1).text("bar").build()) 45 | .build()) 46 | .build(); 47 | } 48 | 49 | private Table createComplexTable1() { 50 | return Table.builder() 51 | .addColumnsOfWidth(50, 50, 50, 50) 52 | .addRow(Row.builder() 53 | .add(TextCell.builder().borderWidth(1).text("1").build()) 54 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds").rowSpan(3).build()) 55 | .add(TextCell.builder().borderWidth(1).text("3").build()) 56 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds even longer more breaks").rowSpan(2).build()) 57 | .build()) 58 | .addRow(Row.builder() 59 | .add(TextCell.builder().borderWidth(1).text("1").build()) 60 | .add(TextCell.builder().borderWidth(1).text("3").build()) 61 | .build()) 62 | .addRow(Row.builder() 63 | .add(TextCell.builder().borderWidth(1).text("fu").build()) 64 | .add(TextCell.builder().borderWidth(1).text("bar").build()) 65 | .add(TextCell.builder().borderWidth(1).text("baz").build()) 66 | .build()) 67 | .addRow(Row.builder() 68 | .add(TextCell.builder().borderWidth(1).text("1").build()) 69 | .add(TextCell.builder().borderWidth(1).text("2").build()) 70 | .add(TextCell.builder().borderWidth(1).text("3").build()) 71 | .add(TextCell.builder().borderWidth(1).text("4").build()) 72 | .build()) 73 | .build(); 74 | } 75 | 76 | private Table createComplexTable2() { 77 | return Table.builder() 78 | .addColumnsOfWidth(50, 50, 50, 50) 79 | .addRow(Row.builder() 80 | .add(TextCell.builder().borderWidth(1).text("1").build()) 81 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds").rowSpan(3).build()) 82 | .add(TextCell.builder().borderWidth(1).text("3").build()) 83 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds even longer more breaks").rowSpan(2).build()) 84 | .build()) 85 | .addRow(Row.builder() 86 | .add(TextCell.builder().borderWidth(1).text("1").build()) 87 | .add(TextCell.builder().borderWidth(1).text("3").build()) 88 | .build()) 89 | .addRow(Row.builder() 90 | .add(TextCell.builder().borderWidth(1).text("fu").build()) 91 | .add(TextCell.builder().borderWidth(1).text("bar").build()) 92 | .add(TextCell.builder().borderWidth(1).text("baz baz baz baz bza bza baz").rowSpan(2).build()) 93 | .build()) 94 | .addRow(Row.builder() 95 | .add(TextCell.builder().borderWidth(1).text("1").build()) 96 | .add(TextCell.builder().borderWidth(1).text("2").build()) 97 | .add(TextCell.builder().borderWidth(1).text("3").build()) 98 | .build()) 99 | .build(); 100 | } 101 | 102 | private Table createComplexTable3() { 103 | return Table.builder() 104 | .addColumnsOfWidth(50, 50, 50, 50) 105 | .addRow(Row.builder() 106 | .add(TextCell.builder().borderWidth(1).text("1").build()) 107 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds").rowSpan(3).build()) 108 | .add(TextCell.builder().borderWidth(1).text("3").build()) 109 | .add(TextCell.builder().borderWidth(1).text("4").build()) 110 | .build()) 111 | .addRow(Row.builder() 112 | .add(TextCell.builder().borderWidth(1).text("1").build()) 113 | .add(TextCell.builder().borderWidth(1).text("3").build()) 114 | .add(TextCell.builder().borderWidth(1).text("fu bza asd fad fda dsafa afa fsdfs fdsfs fds even longer more breaks").rowSpan(2).build()) 115 | .build()) 116 | .addRow(Row.builder() 117 | .add(TextCell.builder().borderWidth(1).text("fu").build()) 118 | .add(TextCell.builder().borderWidth(1).text("bar").build()) 119 | .build()) 120 | .addRow(Row.builder() 121 | .add(TextCell.builder().borderWidth(1).text("1").build()) 122 | .add(TextCell.builder().borderWidth(1).text("2").build()) 123 | .add(TextCell.builder().borderWidth(1).text("3").build()) 124 | .add(TextCell.builder().borderWidth(1).text("4").build()) 125 | .build()) 126 | .build(); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # easytable 2 | 3 | This is a small project that builds upon 4 | [Apache's PDFBox](http://pdfbox.apache.org) (>= 3.0.0) and should allow you 5 | to create tables in a fairly simple way. 6 | It emerged from the need in another project. Therefore, it also may miss some 7 | crucial features. Nevertheless, there is: 8 | 9 | * setting font and font size, table, row, column and cell level 10 | * line breaking and line spacing 11 | * background color and style on table, row, column and cell level 12 | * padding (top, bottom, left, right) on table, row and cell level 13 | * border color, width and style (on table, row or cell level) 14 | * support for text alignment (right, left, center, justified) 15 | * vertical text alignment (top, middle, bottom) 16 | * column spanning and row spanning 17 | * images in cells 18 | * allowing for a lot of customizations 19 | * experimental: vertical text, drawing a large table's 20 | overflow on the same page 21 | 22 | One can also override classes that are responsible for table/cell drawing, i.e. 23 | their drawing behaviour can be customized to a pretty high extent. 24 | 25 | It is also possible to draw a table over multiple pages (even with the 26 | header row being repeated on every new page) or to draw a large table's overflow 27 | next to the already existing table on the same page (see below for examples). 28 | 29 | ## Installation 30 | 31 | Add this to your `pom.xml`: 32 | 33 | 34 | com.github.vandeseer 35 | easytable 36 | 1.0.2 37 | 38 | 39 | Or checkout the repository and install it locally with maven (e.g. for the`develop` branch): 40 | 41 | mvn clean install -DskipTests -Dgpg.skip -Ddependency-check.skip=true 42 | 43 | ## Examples 44 | 45 | There is a [minimal full working example](src/test/java/org/vandeseer/MinimumWorkingExample.java) 46 | which should help you to get started. 47 | 48 | For a bit more involved tables have a look at [this code](src/test/java/org/vandeseer/integrationtest/ExcelLikeExampleTest.java) 49 | which is needed for creating a PDF document with the following two tables: 50 | 51 | ![easytable table](doc/example.png) 52 | 53 | ![easytable table](doc/example2.png) 54 | 55 | For the next example have a look at the [SettingsTest.java](src/test/java/org/vandeseer/integrationtest/settings/SettingsTest.java): 56 | 57 | ![easytable table](doc/example3.png) 58 | 59 | The last one illustrates the use of vertical text in text cells. The code for 60 | it can be found [here](src/test/java/org/vandeseer/integrationtest/VerticalTextCellTest.java): 61 | 62 | ![easytable table](doc/example4.png) 63 | 64 | Drawing the overflow of a large table on the same page is also [possible](src/test/java/org/vandeseer/integrationtest/OverflowOnSamePageTableDrawerTest.java): 65 | 66 | ![easytable table](doc/example_overflow_on_same_page.png) 67 | 68 | If you run the tests with `mvn clean test` there also some PDF documents created which you can find in the `target` folder. 69 | The corresponding sources (in order to understand how to use the code) can be found in the test package. 70 | 71 | ## Kudos 72 | 73 | * to [Binghammer](https://github.com/Binghammer) for implementing cell coloring and text center alignment 74 | * to [Sebastian Göhring](https://github.com/TheSilentHorizon) for finding and fixing a bug (column spanning) 75 | * to [AndreKoepke](https://github.com/AndreKoepke) for the line breaking feature, some bigger nice refactorings and 76 | improvements 77 | * to [Wolfgang Apolinarski](https://github.com/wapolinar) for the printing over pages and bugfixes 78 | * to [AdrianMiska](https://github.com/AdrianMiska) for finding and fixing an issue with cell height 79 | * to [TheRealSourceSeeker](https://github.com/TheRealSourceSeeker) for finding a bug caused by using `float`s 80 | * to [Drummond Dawson](https://github.com/drumonii) for code changes that allowed removing a dependency 81 | * to [styssi](https://github.com/styssi) for allowing several multipage tables being drawn on the same page 82 | * to [Richard Mealing](https://github.com/mealingr) for adding the license section to the `pom.xml` 83 | * to [msww](https://github.com/msww) for finding a [small issue](https://github.com/vandeseer/easytable/issues/85) 84 | * to [VakhoQ](https://github.com/VakhoQ) for implementing border styles 85 | * to [Miloš Čadek](https://github.com/c4da) for implementing alignment of vertical text cells 86 | * to [Chemmic](https://github.com/Chemmic) for adding repeating headers to tables that overflow on the same page 87 | 88 | ## Q&A 89 | 90 | ### Can I use the library with version PDFBox 2.x.x? 91 | 92 | Every version of easytable < 1.0.0 is built on PDFBox 2.x.x. 93 | 94 | Note that easytable 1.0.0 and below 95 | do basically only differ in the support PDFBox version, but 96 | not in their feature set. Only exception being the experimental support 97 | for paragraph cells which had to be dropped with the upgrade to PDFBox 3. 98 | 99 | Also note that easytable >= 1.0.0 is built for Java 11 and higher, whereas 100 | lower versions are using Java 8. 101 | 102 | ### Can I customize the drawers for my own specific needs? 103 | 104 | Yep, you can customize the cell drawers itself or (depending on your use case) 105 | you can just create a custom cell. 106 | 107 | For using a customized cell drawer, have a look at 108 | [CustomCellDrawerTest](src/test/java/org/vandeseer/integrationtest/custom/CustomCellDrawerTest.java). 109 | 110 | In case you want to create your own type of cell (which shouldn't really be necessary since the 111 | drawing can be completely adapted) you will need to use [Lombok](https://projectlombok.org/)'s `@SuperBuilder` 112 | annotation. Again, just have a look at the code: 113 | [CustomCellWithCustomDrawerUsingLombokTest](src/test/java/org/vandeseer/integrationtest/custom/CustomCellWithCustomDrawerUsingLombokTest.java) 114 | 115 | ### Can I draw a table over multiple pages? 116 | 117 | Yes, have a look at [TableOverSeveralPagesTest.java](src/test/java/org/vandeseer/integrationtest/TableOverSeveralPagesTest.java). 118 | Just use `startY(...)` and `endY(..)` in order to restrict the vertical part of the page 119 | where the table should be drawn: 120 | 121 | RepeatedHeaderTableDrawer.builder() 122 | .table(createTable()) 123 | .startX(50) 124 | .startY(100F) 125 | .endY(50F) // <-- If the table is bigger, a new page is started 126 | .build() 127 | 128 | ### Is there a way to repeat the header on every page? 129 | 130 | Depending on whether you want to repeat the header row or not you 131 | should use `RepeatedHeaderTableDrawer` or `TableDrawer` respectively. 132 | 133 | ### Can I get the `y` coordinate of the end of a drawn table? 134 | 135 | Yes. Just use the `.getFinalY()` method. Also see [FinalYTest.java](src/test/java/org/vandeseer/integrationtest/FinalYTest.java). 136 | 137 | ### Cool, I like it, can I buy you a beer? 138 | 139 | Yes. Or you can upvote this answer on [stackoverflow](https://stackoverflow.com/questions/28059563/how-to-create-table-using-apache-pdfbox/42612456#42612456). Or: 140 | 141 | 142 | Donate with PayPal 143 | 144 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/util/PdfUtil.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.util; 2 | 3 | 4 | import lombok.AccessLevel; 5 | import lombok.NoArgsConstructor; 6 | import lombok.SneakyThrows; 7 | import org.apache.pdfbox.pdmodel.font.PDFont; 8 | 9 | import java.util.*; 10 | import java.util.stream.Collectors; 11 | 12 | import static org.vandeseer.easytable.util.FloatUtil.isEqualInEpsilon; 13 | 14 | /** 15 | * Provides some helping functions. 16 | */ 17 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 18 | public final class PdfUtil { 19 | 20 | public static final String NEW_LINE_REGEX = "\\r?\\n"; 21 | 22 | 23 | /** 24 | * Computes the width of a String (in points). 25 | * 26 | * @param text Text 27 | * @param font Font of Text 28 | * @param fontSize FontSize of String 29 | * @return Width (in points) 30 | */ 31 | public static float getStringWidth(final String text, final PDFont font, final int fontSize) { 32 | return Arrays.stream(text.split(NEW_LINE_REGEX)) 33 | .max(Comparator.comparing(String::length)) 34 | .map(x -> getWidthOfStringWithoutNewlines(x, font, fontSize)) 35 | .orElseThrow(CouldNotDetermineStringWidthException::new); 36 | } 37 | 38 | @SneakyThrows 39 | private static float getWidthOfStringWithoutNewlines(String text, PDFont font, int fontSize) { 40 | 41 | final List codePointsAsString = text.codePoints() 42 | .mapToObj(codePoint -> new String(new int[]{codePoint}, 0, 1)) 43 | .collect(Collectors.toList()); 44 | 45 | List widths = new ArrayList<>(); 46 | 47 | for (final String codepoint : codePointsAsString) { 48 | try { 49 | widths.add(font.getStringWidth(codepoint) * fontSize / 1000F); 50 | } catch (final IllegalArgumentException e) { 51 | widths.add(font.getStringWidth("–") * fontSize / 1000F); 52 | } 53 | } 54 | 55 | return widths.stream().reduce(0.0f, Float::sum); 56 | } 57 | 58 | 59 | /** 60 | * Computes the height of a font. 61 | * 62 | * @param font Font 63 | * @param fontSize FontSize 64 | * @return Height of font 65 | */ 66 | public static float getFontHeight(final PDFont font, final int fontSize) { 67 | return font.getFontDescriptor().getCapHeight() * fontSize / 1000F; 68 | } 69 | 70 | /** 71 | * Split a text into multiple lines to prevent a text-overflow. 72 | * 73 | * @param text Text 74 | * @param font Used font 75 | * @param fontSize Used fontSize 76 | * @param maxWidth Maximal width of resulting text-lines 77 | * @return A list of lines, where all are smaller than maxWidth 78 | */ 79 | public static List getOptimalTextBreakLines(final String text, final PDFont font, final int fontSize, final float maxWidth) { 80 | final List result = new ArrayList<>(); 81 | 82 | for (final String line : text.split(NEW_LINE_REGEX)) { 83 | if (PdfUtil.doesTextLineFit(line, font, fontSize, maxWidth)) { 84 | result.add(line); 85 | } else { 86 | result.addAll(PdfUtil.wrapLine(line, font, fontSize, maxWidth)); 87 | } 88 | } 89 | 90 | return result; 91 | } 92 | 93 | private static List wrapLine(final String line, final PDFont font, final int fontSize, final float maxWidth) { 94 | if (PdfUtil.doesTextLineFit(line, font, fontSize, maxWidth)) { 95 | return Collections.singletonList(line); 96 | } 97 | 98 | List goodLines = new ArrayList<>(); 99 | Stack allWords = new Stack<>(); 100 | Arrays.asList(line.split("(?<=[\\\\. ,-])")).forEach(allWords::push); 101 | Collections.reverse(allWords); 102 | 103 | while (!allWords.empty()) { 104 | goodLines.add(buildALine(allWords, font, fontSize, maxWidth)); 105 | } 106 | 107 | return goodLines; 108 | } 109 | 110 | private static List splitBySize(final String line, final PDFont font, final int fontSize, final float maxWidth) { 111 | final List returnList = new ArrayList<>(); 112 | 113 | for (int i = line.length() - 1; i > 0; i--) { 114 | final String fittedNewLine = line.substring(0, i) + "-"; 115 | final String remains = line.substring(i); 116 | 117 | if (PdfUtil.doesTextLineFit(fittedNewLine, font, fontSize, maxWidth)) { 118 | returnList.add(fittedNewLine); 119 | returnList.addAll(PdfUtil.wrapLine(remains, font, fontSize, maxWidth)); 120 | 121 | break; 122 | } 123 | } 124 | 125 | return returnList; 126 | } 127 | 128 | 129 | private static String buildALine(final Stack words, 130 | final PDFont font, 131 | final int fontSize, 132 | final float maxWidth) { 133 | 134 | final StringBuilder line = new StringBuilder(); 135 | float width = 0; 136 | 137 | while (!words.empty()) { 138 | float nextWordWidth = getStringWidth(words.peek(), font, fontSize); 139 | 140 | // if a single char on an empty line bigger than the max-size, then there is no split possible. 141 | if (line.length() == 0 && words.peek().length() == 1 && nextWordWidth > maxWidth) { 142 | return words.pop(); 143 | } 144 | 145 | if (doesTextLineFit(width + nextWordWidth, maxWidth)) { 146 | line.append(words.pop()); 147 | width += nextWordWidth; 148 | } else { 149 | break; 150 | } 151 | } 152 | 153 | // no word found -> split by size 154 | if (width == 0 && !words.empty()) { 155 | List cutBySize = splitBySize(words.pop(), font, fontSize, maxWidth); 156 | Collections.reverse(cutBySize); 157 | cutBySize.forEach(words::push); 158 | 159 | return buildALine(words, font, fontSize, maxWidth); 160 | } 161 | 162 | return line.toString().trim(); 163 | } 164 | 165 | private static boolean doesTextLineFit(final String textLine, final PDFont font, final int fontSize, final float maxWidth) { 166 | return doesTextLineFit(PdfUtil.getStringWidth(textLine, font, fontSize), maxWidth); 167 | } 168 | 169 | private static boolean doesTextLineFit(final float stringWidth, final float maxWidth) { 170 | // Conceptually we want to calculate: 171 | // 172 | // maxWidth >= PdfUtil.getStringWidth(line, font, fontSize) 173 | // 174 | // But this does may not work as expected due to floating point arithmetic. 175 | // Hence we use a delta here that is sufficient for our purposes. 176 | 177 | //noinspection SuspiciousNameCombination 178 | if (isEqualInEpsilon(stringWidth, maxWidth)) return true; // we consider the two numbers being equal 179 | 180 | return maxWidth > stringWidth; 181 | } 182 | 183 | 184 | private static class CouldNotDetermineStringWidthException extends RuntimeException { 185 | CouldNotDetermineStringWidthException() { 186 | super(); 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/integrationtest/settings/SettingsOverridingTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.integrationtest.settings; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 6 | import org.junit.Test; 7 | import org.vandeseer.TestUtils; 8 | import org.vandeseer.easytable.structure.Row; 9 | import org.vandeseer.easytable.structure.Table; 10 | import org.vandeseer.easytable.structure.cell.TextCell; 11 | 12 | import java.awt.*; 13 | 14 | import static junit.framework.TestCase.assertTrue; 15 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.*; 16 | import static org.vandeseer.TestUtils.getActualPdfFor; 17 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 18 | import static org.vandeseer.easytable.settings.HorizontalAlignment.CENTER; 19 | 20 | // TODO Tests for 21 | // - padding (table, row, cell) 22 | // - border width (table, row, cell) 23 | public class SettingsOverridingTest { 24 | 25 | public static final String FILE_NAME = "settingsOverriding.pdf"; 26 | 27 | @Test 28 | public void createDocumentWithTables() throws Exception { 29 | TestUtils.createAndSaveDocumentWithTables(FILE_NAME, 30 | createTableWithFontSettingAndBorderColorOverriding(), 31 | createTableWithTextColorAndBackgroundColorOverriding(), 32 | createSampleDocumentWithWordBreakOverriding() 33 | ); 34 | 35 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME), getActualPdfFor(FILE_NAME)).compare(); 36 | assertTrue(compareResult.isEqual()); 37 | } 38 | 39 | 40 | private Table createTableWithFontSettingAndBorderColorOverriding() { 41 | final Table.TableBuilder tableBuilder = Table.builder() 42 | .addColumnsOfWidth(100, 100, 100) 43 | .horizontalAlignment(CENTER) 44 | .fontSize(10).font(new PDType1Font(HELVETICA)) 45 | .borderColor(Color.RED); 46 | 47 | tableBuilder.addRow( 48 | Row.builder() 49 | .add(TextCell.builder().text("FCmjGVylqCjoxxfFWhehSrm").colSpan(2).borderWidth(1).build()) 50 | .add(TextCell.builder().text("Booz").build()) 51 | .font(COURIER_BOLD).fontSize(8) 52 | .build()); 53 | 54 | tableBuilder.addRow( 55 | Row.builder() 56 | .add(TextCell.builder().text("Pur").backgroundColor(Color.YELLOW).build()) 57 | .add(TextCell.builder().text("Booz").build()) 58 | .add(TextCell.builder().text("baz").build()) 59 | .font(new PDType1Font(COURIER_BOLD)).fontSize(8).build()); 60 | 61 | tableBuilder.addRow( 62 | Row.builder() 63 | .add(TextCell.builder().text("Pur").borderWidthRight(1).borderWidthBottom(1).backgroundColor(Color.YELLOW).build()) 64 | .add(TextCell.builder().text("Booz").borderWidthTop(1).borderWidthLeft(1).build()) 65 | .add(TextCell.builder().text("baz").borderWidthBottom(1).build()) 66 | .borderColor(Color.GREEN) 67 | .font(new PDType1Font(COURIER_BOLD)).fontSize(8).build()); 68 | 69 | tableBuilder.addRow( 70 | Row.builder() 71 | .add(TextCell.builder().text("Pur").backgroundColor(Color.YELLOW).borderWidth(1).build()) 72 | .add(TextCell.builder().text("Booz").build()) 73 | .add(TextCell.builder().text("baz").borderColor(Color.CYAN).borderWidthBottom(1) 74 | .font(HELVETICA_OBLIQUE).fontSize(5).build()) 75 | .build()); 76 | 77 | return tableBuilder.build(); 78 | } 79 | 80 | private Table createTableWithTextColorAndBackgroundColorOverriding() { 81 | final Table.TableBuilder tableBuilder = Table.builder() 82 | .addColumnsOfWidth(100, 100, 100) 83 | .horizontalAlignment(CENTER) 84 | .fontSize(10).font(new PDType1Font(HELVETICA)) 85 | .textColor(Color.GREEN) 86 | .backgroundColor(Color.LIGHT_GRAY); 87 | 88 | tableBuilder.addRow( 89 | Row.builder() 90 | .add(TextCell.builder().text("OWpTlEgQPoSmoyjdNcQdVbc").colSpan(2).borderWidth(1).build()) 91 | .add(TextCell.builder().text("Booz").borderWidth(1).build()) 92 | .build()); 93 | 94 | tableBuilder.addRow( 95 | Row.builder() 96 | .add(TextCell.builder().text("Pur").borderWidth(1).build()) 97 | .add(TextCell.builder().text("Booz").borderWidth(1).build()) 98 | .add(TextCell.builder().text("baz").borderWidth(1).build()) 99 | .backgroundColor(Color.YELLOW) 100 | .textColor(Color.BLACK) 101 | .build()); 102 | 103 | tableBuilder.addRow( 104 | Row.builder() 105 | .add(TextCell.builder().text("Pur").borderWidth(1).backgroundColor(Color.ORANGE).build()) 106 | .add(TextCell.builder().text("Booz").borderWidth(1).build()) 107 | .add(TextCell.builder().text("baz").borderWidth(1).textColor(Color.PINK).build()) 108 | .build()); 109 | 110 | return tableBuilder.build(); 111 | } 112 | 113 | private Table createSampleDocumentWithWordBreakOverriding() { 114 | final Table.TableBuilder tableBuilder = Table.builder() 115 | .addColumnsOfWidth(150, 150, 150) 116 | .horizontalAlignment(CENTER); 117 | 118 | tableBuilder.addRow( 119 | Row.builder() 120 | .add(TextCell.builder().text("This is long and should therefore break. This is long and should therefore break. This is long and should therefore break.") 121 | .colSpan(2).borderWidth(1).wordBreak(true).build()) 122 | .add(TextCell.builder().text("This, too, is too long for not breaking.").borderWidth(1).build()) 123 | .wordBreak(false) 124 | .build()); 125 | 126 | tableBuilder.addRow( 127 | Row.builder() 128 | .add(TextCell.builder().text("This should break, because the row uses overriding.").borderWidth(1).build()) 129 | .add(TextCell.builder().text("This uses the overriding and should hence not break!").borderWidth(1).wordBreak(false).build()) 130 | .add(TextCell.builder().text("This should break, because the row uses overriding.").borderWidth(1).build()) 131 | .build()); 132 | 133 | tableBuilder.addRow( 134 | Row.builder() 135 | .add(TextCell.builder().text("This should break, because the table uses overriding.").borderWidth(1).build()) 136 | .add(TextCell.builder().text("This should break, because the table uses overriding.").borderWidth(1).build()) 137 | .add(TextCell.builder().text("This should break, because the table uses overriding.").borderWidth(1).build()) 138 | .build()); 139 | 140 | return tableBuilder.build(); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/org/vandeseer/easytable/drawing/cell/AbstractCellDrawer.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.easytable.drawing.cell; 2 | 3 | import lombok.SneakyThrows; 4 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 5 | import org.vandeseer.easytable.drawing.*; 6 | import org.vandeseer.easytable.structure.cell.AbstractCell; 7 | import org.vandeseer.easytable.util.FloatUtil; 8 | 9 | import java.awt.*; 10 | import java.awt.geom.Point2D; 11 | 12 | import static org.vandeseer.easytable.settings.VerticalAlignment.BOTTOM; 13 | import static org.vandeseer.easytable.settings.VerticalAlignment.MIDDLE; 14 | 15 | public abstract class AbstractCellDrawer implements Drawer { 16 | 17 | protected T cell; 18 | 19 | public AbstractCellDrawer withCell(T cell) { 20 | this.cell = cell; 21 | return this; 22 | } 23 | 24 | @Override 25 | @SneakyThrows 26 | public void drawBackground(DrawingContext drawingContext) { 27 | if (cell.hasBackgroundColor()) { 28 | final PDPageContentStream contentStream = drawingContext.getContentStream(); 29 | final Point2D.Float start = drawingContext.getStartingPoint(); 30 | 31 | final float rowHeight = cell.getRow().getHeight(); 32 | final float height = Math.max(cell.getHeight(), rowHeight); 33 | final float y = rowHeight < cell.getHeight() 34 | ? start.y + rowHeight - cell.getHeight() 35 | : start.y; 36 | 37 | DrawingUtil.drawRectangle(contentStream, 38 | PositionedRectangle.builder() 39 | .x(start.x) 40 | .y(y) 41 | .width(cell.getWidth()) 42 | .height(height) 43 | .color(cell.getBackgroundColor()) 44 | .build() 45 | ); 46 | } 47 | } 48 | 49 | @Override 50 | public abstract void drawContent(DrawingContext drawingContext); 51 | 52 | @Override 53 | @SneakyThrows 54 | public void drawBorders(DrawingContext drawingContext) { 55 | final Point2D.Float start = drawingContext.getStartingPoint(); 56 | final PDPageContentStream contentStream = drawingContext.getContentStream(); 57 | 58 | final float cellWidth = cell.getWidth(); 59 | 60 | final float rowHeight = cell.getRow().getHeight(); 61 | final float height = Math.max(cell.getHeight(), rowHeight); 62 | final float sY = rowHeight < cell.getHeight() 63 | ? start.y + rowHeight - cell.getHeight() 64 | : start.y; 65 | 66 | // Handle the cell's borders 67 | final Color cellBorderColorTop = cell.getBorderColorTop(); 68 | final Color cellBorderColorBottom = cell.getBorderColorBottom(); 69 | final Color cellBorderColorLeft = cell.getBorderColorLeft(); 70 | final Color cellBorderColorRight = cell.getBorderColorRight(); 71 | 72 | final Color rowBorderColor = cell.getRow().getBorderColor(); 73 | 74 | if (cell.hasBorderTop() || cell.hasBorderBottom()) { 75 | final float correctionLeft = cell.getBorderWidthLeft() / 2; 76 | final float correctionRight = cell.getBorderWidthRight() / 2; 77 | 78 | if (cell.hasBorderTop()) { 79 | DrawingUtil.drawLine(contentStream, PositionedLine.builder() 80 | .startX(start.x - correctionLeft) 81 | .startY(start.y + rowHeight) 82 | .endX(start.x + cellWidth + correctionRight) 83 | .endY(start.y + rowHeight) 84 | .width(cell.getBorderWidthTop()) 85 | .color(cellBorderColorTop) 86 | .resetColor(rowBorderColor) 87 | .borderStyle(cell.getBorderStyleTop()) 88 | .build() 89 | ); 90 | } 91 | 92 | if (cell.hasBorderBottom()) { 93 | DrawingUtil.drawLine(contentStream, PositionedLine.builder() 94 | .startX(start.x - correctionLeft) 95 | .startY(sY) 96 | .endX(start.x + cellWidth + correctionRight) 97 | .endY(sY) 98 | .width(cell.getBorderWidthBottom()) 99 | .color(cellBorderColorBottom) 100 | .resetColor(rowBorderColor) 101 | .borderStyle(cell.getBorderStyleBottom()) 102 | .build() 103 | ); 104 | } 105 | } 106 | 107 | if (cell.hasBorderLeft() || cell.hasBorderRight()) { 108 | final float correctionTop = cell.getBorderWidthTop() / 2; 109 | final float correctionBottom = cell.getBorderWidthBottom() / 2; 110 | 111 | if (cell.hasBorderLeft()) { 112 | DrawingUtil.drawLine(contentStream, PositionedLine.builder() 113 | .startX(start.x) 114 | .startY(sY - correctionBottom) 115 | .endX(start.x) 116 | .endY(sY + height + correctionTop) 117 | .width(cell.getBorderWidthLeft()) 118 | .color(cellBorderColorLeft) 119 | .resetColor(rowBorderColor) 120 | .borderStyle(cell.getBorderStyleLeft()) 121 | .build() 122 | ); 123 | } 124 | 125 | if (cell.hasBorderRight()) { 126 | DrawingUtil.drawLine(contentStream, PositionedLine.builder() 127 | .startX(start.x + cellWidth) 128 | .startY(sY - correctionBottom) 129 | .endX(start.x + cellWidth) 130 | .endY(sY + height + correctionTop) 131 | .width(cell.getBorderWidthRight()) 132 | .color(cellBorderColorRight) 133 | .resetColor(rowBorderColor) 134 | .borderStyle(cell.getBorderStyleRight()) 135 | .build() 136 | ); 137 | } 138 | } 139 | } 140 | 141 | protected boolean rowHeightIsBiggerThanOrEqualToCellHeight() { 142 | return cell.getRow().getHeight() > cell.getHeight() 143 | || FloatUtil.isEqualInEpsilon(cell.getRow().getHeight(), cell.getHeight()); 144 | } 145 | 146 | protected float getRowSpanAdaption() { 147 | return cell.getRowSpan() > 1 148 | ? cell.calculateHeightForRowSpan() - cell.getRow().getHeight() 149 | : 0; 150 | } 151 | 152 | protected float calculateOuterHeight() { 153 | return cell.getRowSpan() > 1 ? cell.getHeight() : cell.getRow().getHeight(); 154 | } 155 | 156 | protected float getAdaptionForVerticalAlignment() { 157 | if (rowHeightIsBiggerThanOrEqualToCellHeight() || cell.getRowSpan() > 1) { 158 | 159 | if (cell.isVerticallyAligned(MIDDLE)) { 160 | return (calculateOuterHeight() / 2 + calculateInnerHeight() / 2) - getRowSpanAdaption(); 161 | 162 | } else if (cell.isVerticallyAligned(BOTTOM)) { 163 | return (calculateInnerHeight() + cell.getPaddingBottom()) - getRowSpanAdaption(); 164 | } 165 | } 166 | 167 | // top alignment (default case) 168 | return cell.getRow().getHeight() - cell.getPaddingTop(); // top position 169 | } 170 | 171 | protected abstract float calculateInnerHeight(); 172 | } 173 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | 6 | 7 | org.sonatype.oss 8 | oss-parent 9 | 9 10 | 11 | 12 | com.github.vandeseer 13 | easytable 14 | 1.0.2 15 | jar 16 | 17 | 18 | UTF-8 19 | 20 | 21 | 22 | 23 | MIT License 24 | https://opensource.org/licenses/MIT 25 | repo 26 | 27 | 28 | 29 | 30 | 31 | ossrh 32 | https://oss.sonatype.org/content/repositories/snapshots 33 | 34 | 35 | ossrh 36 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 37 | 38 | 39 | 40 | 41 | 42 | org.apache.pdfbox 43 | pdfbox 44 | 3.0.2 45 | compile 46 | 47 | 48 | org.projectlombok 49 | lombok 50 | 1.18.32 51 | provided 52 | 53 | 54 | 55 | junit 56 | junit 57 | 58 | 4.13.2 59 | test 60 | 61 | 62 | 63 | org.hamcrest 64 | hamcrest 65 | 2.2 66 | test 67 | 68 | 69 | org.hamcrest 70 | hamcrest-library 71 | 2.2 72 | test 73 | 74 | 75 | 76 | org.mockito 77 | mockito-core 78 | 5.11.0 79 | test 80 | 81 | 82 | de.redsix 83 | pdfcompare 84 | 1.2.2 85 | test 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | org.codehaus.mojo 95 | versions-maven-plugin 96 | 2.16.2 97 | false 98 | 99 | 100 | 101 | 102 | org.owasp 103 | dependency-check-maven 104 | 9.0.6 105 | 106 | 4 107 | false 108 | 109 | 110 | 111 | 112 | check 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-compiler-plugin 121 | 3.8.0 122 | 123 | 11 124 | 11 125 | 126 | 127 | org.projectlombok 128 | lombok 129 | 1.18.32 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-source-plugin 137 | 3.0.1 138 | 139 | 140 | attach-sources 141 | 142 | jar 143 | 144 | 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-javadoc-plugin 150 | 3.6.3 151 | 152 | 153 | attach-javadocs 154 | 155 | jar 156 | 157 | 158 | 159 | 160 | 161 | org.apache.maven.plugins 162 | maven-gpg-plugin 163 | 1.6 164 | 165 | 166 | sign-artifacts 167 | verify 168 | 169 | sign 170 | 171 | 172 | 173 | 174 | 175 | org.sonatype.plugins 176 | nexus-staging-maven-plugin 177 | 1.6.13 178 | true 179 | 180 | ossrh 181 | https://oss.sonatype.org/ 182 | true 183 | 184 | 185 | 186 | 187 | 188 | org.apache.maven.plugins 189 | maven-surefire-plugin 190 | 2.19.1 191 | 192 | false 193 | 194 | 195 | 196 | 197 | 198 | 199 | maven_central 200 | Maven Central 201 | https://repo.maven.apache.org/maven2/ 202 | 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/test/java/org/vandeseer/regressiontest/Issue20WordWrappingTest.java: -------------------------------------------------------------------------------- 1 | package org.vandeseer.regressiontest; 2 | 3 | import de.redsix.pdfcompare.CompareResult; 4 | import de.redsix.pdfcompare.PdfComparator; 5 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.vandeseer.TestUtils; 9 | import org.vandeseer.easytable.structure.Row; 10 | import org.vandeseer.easytable.structure.Table; 11 | import org.vandeseer.easytable.structure.cell.TextCell; 12 | 13 | import java.awt.*; 14 | import java.io.IOException; 15 | 16 | import static junit.framework.TestCase.assertTrue; 17 | import static org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA; 18 | import static org.vandeseer.TestUtils.getActualPdfFor; 19 | import static org.vandeseer.TestUtils.getExpectedPdfFor; 20 | import static org.vandeseer.easytable.settings.HorizontalAlignment.*; 21 | 22 | public class Issue20WordWrappingTest { 23 | 24 | private static final String FILE_NAME_TEST_1 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" + "wordWrapping_issue20.pdf"; 25 | private static final String FILE_NAME_TEST_2 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" + "wordWrapping_issue20_2.pdf"; 26 | private static final String FILE_NAME_TEST_3 = TestUtils.TARGET_SUBFOLDER_REGRESSION + "/" + "wordWrapping_issue20_3.pdf"; 27 | 28 | @Before 29 | public void before() { 30 | TestUtils.assertRegressionFolderExists(); 31 | } 32 | 33 | @Test 34 | public void createSampleDocumentWithWordWrapping_Issue20() throws Exception { 35 | final Table.TableBuilder tableBuilder = Table.builder() 36 | .addColumnsOfWidth(8, 8, 8) 37 | .fontSize(12) 38 | .font(new PDType1Font(HELVETICA)) 39 | .borderColor(Color.RED) 40 | .wordBreak(true); 41 | 42 | tableBuilder.addRow(Row.builder() 43 | .add(TextCell.builder().text("a").borderWidth(1).padding(0f).horizontalAlignment(CENTER).build()) 44 | .add(TextCell.builder().text("B").borderWidth(1).padding(0f).build()) 45 | .add(TextCell.builder().text("#").borderWidth(1).padding(0f).build()) 46 | .build()); 47 | 48 | tableBuilder.addRow(Row.builder() 49 | .add(TextCell.builder().text("c").borderWidth(1).padding(0f).horizontalAlignment(RIGHT).build()) 50 | .add(TextCell.builder().text("D").borderWidth(1).padding(0f).build()) 51 | .add(TextCell.builder().text("g").borderWidth(1).padding(0f).build()) 52 | .build()); 53 | 54 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME_TEST_1, tableBuilder.build()); 55 | 56 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME_TEST_1), getActualPdfFor(FILE_NAME_TEST_1)).compare(); 57 | assertTrue(compareResult.isEqual()); 58 | } 59 | 60 | @Test 61 | public void createSampleDocumentWithWordWrapping_Issue20_2() throws Exception { 62 | final Table.TableBuilder tableBuilder = Table.builder() 63 | .addColumnsOfWidth(70, 260, 70, 70) 64 | .fontSize(12) 65 | .font(new PDType1Font(HELVETICA)) 66 | .borderColor(Color.WHITE) 67 | .wordBreak(true); 68 | 69 | tableBuilder.addRow(Row.builder() 70 | .add(TextCell.builder().borderWidth(1).text("Stückzahl").build()) 71 | .add(TextCell.builder().borderWidth(1).horizontalAlignment(CENTER).text("Artikel Bez.").build()) 72 | .add(TextCell.builder().borderWidth(1).horizontalAlignment(CENTER).text("Einzelpreis").build()) 73 | .add(TextCell.builder().borderWidth(1).horizontalAlignment(CENTER).text("Gesamt").build()) 74 | .backgroundColor(Color.LIGHT_GRAY) 75 | .build()); 76 | 77 | for (int i = 0; i < 10; i++) { 78 | int price = 200; 79 | tableBuilder.addRow(Row.builder() 80 | .add(TextCell.builder().text(String.valueOf(i + 1)).horizontalAlignment(RIGHT).build()) 81 | .add(TextCell.builder().text("Thermaltake Desktop Tenor VB 2000 SNS").build()) 82 | .add(TextCell.builder().text(price + " €").horizontalAlignment(RIGHT).build()) 83 | .add(TextCell.builder().text((i + 1) * price + " €").horizontalAlignment(RIGHT).build()) 84 | .build()); 85 | } 86 | 87 | tableBuilder.addRow(Row.builder() 88 | .add(TextCell.builder().text(String.valueOf(3)).horizontalAlignment(RIGHT).build()) 89 | .add(TextCell.builder().text("This breaks because it is too long Thermaltake Desktop Tenor VB 2000 SNS").build()) 90 | .add(TextCell.builder().text(120 + " €").horizontalAlignment(RIGHT).build()) 91 | .add(TextCell.builder().text(3 * 120 + " €").horizontalAlignment(RIGHT).build()) 92 | .build()); 93 | 94 | tableBuilder.addRow(Row.builder() 95 | .add(TextCell.builder().text(String.valueOf(3)).horizontalAlignment(RIGHT).build()) 96 | .add(TextCell.builder().text("Fubar Article").build()) 97 | .add(TextCell.builder().text(234 + " €").horizontalAlignment(RIGHT).build()) 98 | .add(TextCell.builder().text(3 * 234 + " €").horizontalAlignment(RIGHT).build()) 99 | .build()); 100 | 101 | tableBuilder.addRow(Row.builder() 102 | .add(TextCell.builder().text("This is a text that spans over four columns. Hooray! Also it breaks" + 103 | "because it is too long. This is a text that spans over four columns. Hooray! " + 104 | "Also it breaks.").colSpan(4).build()) 105 | .backgroundColor(Color.LIGHT_GRAY) 106 | .build()); 107 | 108 | for (int i = 0; i < 5; i++) { 109 | int price = 429; 110 | tableBuilder.addRow(Row.builder() 111 | .add(TextCell.builder().text(String.valueOf(i + 1)).horizontalAlignment(RIGHT).build()) 112 | .add(TextCell.builder().text("Some other fancy product").build()) 113 | .add(TextCell.builder().text(price + " €").horizontalAlignment(RIGHT).build()) 114 | .add(TextCell.builder().text((i + 1) * price + " €").horizontalAlignment(RIGHT).build()) 115 | .build()); 116 | } 117 | 118 | tableBuilder.addRow(Row.builder() 119 | .add(TextCell.builder().text("Gesamt Seite 1:").horizontalAlignment(RIGHT).colSpan(3).borderWidth(1).build()) 120 | .add(TextCell.builder().text("1 600 €").horizontalAlignment(RIGHT).borderWidth(1).build()) 121 | .backgroundColor(Color.LIGHT_GRAY) 122 | .build()); 123 | 124 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME_TEST_2, tableBuilder.build()); 125 | 126 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME_TEST_2), getActualPdfFor(FILE_NAME_TEST_2)).compare(); 127 | assertTrue(compareResult.isEqual()); 128 | } 129 | 130 | @Test 131 | public void issue20() throws IOException { 132 | final Table.TableBuilder tableBuilder = Table.builder() 133 | .addColumnsOfWidth(100, 100) 134 | .fontSize(8) 135 | .font(new PDType1Font(HELVETICA)) 136 | .addRow(Row.builder() 137 | .add(TextCell.builder().text("K.").horizontalAlignment(LEFT).borderWidth(1).build()) 138 | .add(TextCell.builder().text("fubar").horizontalAlignment(LEFT).borderWidth(1).build()) 139 | .horizontalAlignment(RIGHT) 140 | .build()) 141 | .wordBreak(true); 142 | 143 | TestUtils.createAndSaveDocumentWithTable(FILE_NAME_TEST_3, tableBuilder.build()); 144 | 145 | CompareResult compareResult = new PdfComparator<>(getExpectedPdfFor(FILE_NAME_TEST_3), getActualPdfFor(FILE_NAME_TEST_3)).compare(); 146 | assertTrue(compareResult.isEqual()); 147 | } 148 | 149 | } 150 | --------------------------------------------------------------------------------