├── .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 | 
52 |
53 | 
54 |
55 | For the next example have a look at the [SettingsTest.java](src/test/java/org/vandeseer/integrationtest/settings/SettingsTest.java):
56 |
57 | 
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 | 
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 | 
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 |
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 |
--------------------------------------------------------------------------------