├── .travis.yml ├── FUNDING.yml ├── gradle.properties ├── src ├── test │ ├── resources │ │ ├── 150dpi.png │ │ └── 300dpi.png │ └── java │ │ └── be │ │ └── quodlibet │ │ └── boxable │ │ ├── text │ │ └── TokenizerTest.java │ │ └── DataTableTest.java └── main │ ├── resources │ └── fonts │ │ ├── FreeMono.ttf │ │ ├── FreeSans.ttf │ │ ├── FreeSerif.ttf │ │ ├── FreeMonoBold.ttf │ │ ├── FreeSansBold.ttf │ │ ├── FreeSerifBold.ttf │ │ ├── FreeMonoOblique.ttf │ │ ├── FreeSansOblique.ttf │ │ ├── FreeSerifItalic.ttf │ │ ├── FreeMonoBoldOblique.ttf │ │ ├── FreeSansBoldOblique.ttf │ │ ├── FreeSerifBoldItalic.ttf │ │ ├── INSTALL │ │ ├── TROUBLESHOOTING │ │ ├── README │ │ ├── USAGE │ │ └── AUTHORS │ └── java │ └── be │ └── quodlibet │ └── boxable │ ├── text │ ├── WrappingFunction.java │ ├── TokenType.java │ ├── Token.java │ ├── PipelineLayer.java │ └── Tokenizer.java │ ├── TextType.java │ ├── page │ ├── PageProvider.java │ └── DefaultPageProvider.java │ ├── CellContentDrawnListener.java │ ├── VerticalAlignment.java │ ├── HorizontalAlignment.java │ ├── datatable │ ├── UpdateCellProperty.java │ └── DataTable.java │ ├── HTMLListNode.java │ ├── BoxableUtils.java │ ├── AbstractPageTemplate.java │ ├── ImageCell.java │ ├── BaseTable.java │ ├── AbstractTemplatedTable.java │ ├── utils │ ├── ImageUtils.java │ ├── PDStreamUtils.java │ ├── PageContentStreamOptimized.java │ └── FontUtils.java │ ├── line │ └── LineStyle.java │ ├── image │ └── Image.java │ ├── Row.java │ ├── TableCell.java │ └── Cell.java ├── .gitignore ├── .project ├── .classpath ├── README.md ├── pom.xml └── COPYING /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github : quodlibet 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | netbeans.org-netbeans-modules-javascript2-requirejs.enabled=true 2 | -------------------------------------------------------------------------------- /src/test/resources/150dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/test/resources/150dpi.png -------------------------------------------------------------------------------- /src/test/resources/300dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/test/resources/300dpi.png -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeMono.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSans.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSerif.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeMonoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeMonoBold.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSansBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSansBold.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSerifBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSerifBold.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeMonoOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeMonoOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSansOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSansOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSerifItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSerifItalic.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeMonoBoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeMonoBoldOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSansBoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSansBoldOblique.ttf -------------------------------------------------------------------------------- /src/main/resources/fonts/FreeSerifBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhorions/boxable/HEAD/src/main/resources/fonts/FreeSerifBoldItalic.ttf -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/text/WrappingFunction.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | public interface WrappingFunction { 4 | 5 | String[] getLines(String text); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/text/TokenType.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | public enum TokenType { 4 | TEXT, POSSIBLE_WRAP_POINT, WRAP_POINT, OPEN_TAG, CLOSE_TAG, PADDING, BULLET, ORDERING 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/TextType.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | /** 4 | * Created by dgautier on 3/11/2015. 5 | */ 6 | public enum TextType { 7 | HIGHLIGHT,UNDERLINE,SQUIGGLY,STRIKEOUT; 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pdf 2 | build/* 3 | dist/* 4 | *.class 5 | lib/* 6 | nbproject/* 7 | build.xml 8 | manifest.mf 9 | # Package Files # 10 | *.jar 11 | *.war 12 | *.ear 13 | 14 | .idea 15 | target 16 | *.iml 17 | /bin/ 18 | /build/ 19 | .settings 20 | .gradle 21 | .metadata 22 | settings.xml 23 | nbactions.xml 24 | nb-configuration.xml 25 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/page/PageProvider.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.page; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | 6 | public interface PageProvider { 7 | 8 | T createPage(); 9 | 10 | T nextPage(); 11 | 12 | T previousPage(); 13 | 14 | PDDocument getDocument(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/CellContentDrawnListener.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 6 | 7 | public interface CellContentDrawnListener { 8 | void onContentDrawn(Cell cell, PDDocument document, PDPage page, PDRectangle rectangle); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/VerticalAlignment.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | public enum VerticalAlignment { 4 | TOP, MIDDLE, BOTTOM; 5 | 6 | public static VerticalAlignment get(final String key) { 7 | switch (key == null ? "top" : key.toLowerCase().trim()) { 8 | case "top": 9 | return TOP; 10 | case "middle": 11 | return MIDDLE; 12 | case "bottom": 13 | return BOTTOM; 14 | default: 15 | return TOP; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/HorizontalAlignment.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | public enum HorizontalAlignment { 4 | LEFT, CENTER, RIGHT; 5 | 6 | public static HorizontalAlignment get(final String key) { 7 | switch (key == null ? "left" : key.toLowerCase().trim()) { 8 | case "left": 9 | return LEFT; 10 | case "center": 11 | return CENTER; 12 | case "right": 13 | return RIGHT; 14 | default: 15 | return LEFT; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/datatable/UpdateCellProperty.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.datatable; 2 | 3 | import org.apache.pdfbox.pdmodel.PDPage; 4 | 5 | import be.quodlibet.boxable.Cell; 6 | 7 | /** 8 | * Allows changing the cell properties, while the CSV documents is written directly into the PDF tables 9 | * 10 | * @author Christoph Schemmelmann {@code } 11 | */ 12 | public interface UpdateCellProperty { 13 | 14 | void updateCellPropertiesAtColumn(Cell cell, int column, int row); 15 | } -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | boxable 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.springsource.ide.eclipse.gradle.core.nature 21 | org.eclipse.jdt.core.javanature 22 | org.eclipse.jdt.groovy.core.groovyNature 23 | org.eclipse.buildship.core.gradleprojectnature 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/HTMLListNode.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | /** 4 | *

5 | * Data container for HTML ordered list elements. 6 | *

7 | * 8 | * @author hstimac 9 | * 10 | */ 11 | public class HTMLListNode { 12 | 13 | /** 14 | *

15 | * Element's current ordering number (e.g third element in the current list) 16 | *

17 | */ 18 | private int orderingNumber; 19 | 20 | /** 21 | *

22 | * Element's whole ordering number value (e.g 1.1.2.1) 23 | *

24 | */ 25 | private String value; 26 | 27 | public int getOrderingNumber() { 28 | return orderingNumber; 29 | } 30 | 31 | public void setOrderingNumber(int orderingNumber) { 32 | this.orderingNumber = orderingNumber; 33 | } 34 | 35 | public String getValue() { 36 | return value; 37 | } 38 | 39 | public void setValue(String value) { 40 | this.value = value; 41 | } 42 | 43 | public HTMLListNode(int orderingNumber, String value) { 44 | this.orderingNumber = orderingNumber; 45 | this.value = value; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/BoxableUtils.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.font.PDType0Font; 7 | 8 | import be.quodlibet.boxable.utils.FontUtils; 9 | 10 | /** 11 | * Created by dgautier on 3/19/2015. 12 | * 13 | * @deprecated All methods have been moved elsewhere, see each method for more 14 | * details 15 | */ 16 | @Deprecated 17 | public class BoxableUtils { 18 | 19 | /** 20 | * @deprecated Use {@link FontUtils#loadFont(PDDocument, String)} instead 21 | * @param document {@link PDDocument} where this {@link PDType0Font} will be applied 22 | * @param fontPath font's path 23 | * @return {@link PDType0Font} 24 | * @throws IOException if font's path is bad 25 | */ 26 | @Deprecated 27 | public static final PDType0Font loadFont(PDDocument document, String fontPath) throws IOException { 28 | return PDType0Font.load(document, BoxableUtils.class.getClassLoader().getResourceAsStream(fontPath)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/AbstractPageTemplate.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import java.io.IOException; 4 | 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.graphics.image.PDImage; 9 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 10 | 11 | /** 12 | * Created by dgautier on 3/18/2015. 13 | */ 14 | public abstract class AbstractPageTemplate extends PDPage { 15 | 16 | protected abstract PDDocument getDocument(); 17 | 18 | protected abstract float yStart(); 19 | 20 | protected void addPicture(PDImageXObject ximage, float cursorX, float cursorY, int width, int height) throws IOException { 21 | 22 | PDPageContentStream contentStream = new PDPageContentStream(getDocument(), this, 23 | PDPageContentStream.AppendMode.APPEND, false); 24 | contentStream.drawImage(ximage, cursorX, cursorY, width, height); 25 | contentStream.close(); 26 | } 27 | 28 | protected PDImage loadPicture(String nameJPGFile) throws IOException { 29 | return PDImageXObject.createFromFile(nameJPGFile, getDocument()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/page/DefaultPageProvider.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.page; 2 | 3 | import org.apache.pdfbox.pdmodel.PDDocument; 4 | import org.apache.pdfbox.pdmodel.PDPage; 5 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 6 | 7 | public class DefaultPageProvider implements PageProvider { 8 | 9 | private final PDDocument document; 10 | 11 | private final PDRectangle size; 12 | 13 | private int currentPageIndex = -1; 14 | 15 | public DefaultPageProvider(final PDDocument document, final PDRectangle size) { 16 | this.document = document; 17 | this.size = size; 18 | } 19 | 20 | @Override 21 | public PDDocument getDocument() { 22 | return document; 23 | } 24 | 25 | @Override 26 | public PDPage createPage() { 27 | currentPageIndex = document.getNumberOfPages(); 28 | return getCurrentPage(); 29 | } 30 | 31 | @Override 32 | public PDPage nextPage() { 33 | if (currentPageIndex == -1) { 34 | currentPageIndex = document.getNumberOfPages(); 35 | } else { 36 | currentPageIndex++; 37 | } 38 | 39 | return getCurrentPage(); 40 | } 41 | 42 | @Override 43 | public PDPage previousPage() { 44 | currentPageIndex--; 45 | if (currentPageIndex < 0) { 46 | currentPageIndex = 0; 47 | } 48 | 49 | return getCurrentPage(); 50 | } 51 | 52 | private PDPage getCurrentPage() { 53 | if (currentPageIndex >= document.getNumberOfPages()) { 54 | final PDPage newPage = new PDPage(size); 55 | document.addPage(newPage); 56 | return newPage; 57 | } 58 | 59 | return document.getPage(currentPageIndex); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/ImageCell.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import org.apache.pdfbox.pdmodel.PDPage; 4 | 5 | import be.quodlibet.boxable.image.Image; 6 | 7 | public class ImageCell extends Cell { 8 | 9 | private Image img; 10 | 11 | private final HorizontalAlignment align; 12 | 13 | private final VerticalAlignment valign; 14 | 15 | ImageCell(Row row, float width, Image image, boolean isCalculated) { 16 | super(row, width, null, isCalculated); 17 | this.img = image; 18 | if(image.getWidth() > getInnerWidth()){ 19 | scaleToFit(); 20 | } 21 | this.align = HorizontalAlignment.LEFT; 22 | this.valign = VerticalAlignment.TOP; 23 | } 24 | 25 | public void scaleToFit() { 26 | img = img.scale(getInnerWidth()); 27 | } 28 | 29 | ImageCell(Row row, float width, Image image, boolean isCalculated, HorizontalAlignment align, 30 | VerticalAlignment valign) { 31 | super(row, width, null, isCalculated, align, valign); 32 | this.img = image; 33 | if(image.getWidth() > getInnerWidth()){ 34 | scaleToFit(); 35 | } 36 | this.align = align; 37 | this.valign = valign; 38 | } 39 | 40 | @Override 41 | public float getTextHeight() { 42 | return img.getHeight(); 43 | } 44 | 45 | @Override 46 | public float getHorizontalFreeSpace() { 47 | return getInnerWidth() - img.getWidth(); 48 | } 49 | 50 | @Override 51 | public float getVerticalFreeSpace() { 52 | return getInnerHeight() - img.getHeight(); 53 | } 54 | 55 | 56 | /** 57 | *

58 | * Method which retrieve {@link Image} 59 | *

60 | * 61 | * @return {@link Image} 62 | */ 63 | public Image getImage() { 64 | return img; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/BaseTable.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | import org.apache.pdfbox.pdmodel.PDPage; 7 | 8 | import be.quodlibet.boxable.page.DefaultPageProvider; 9 | import be.quodlibet.boxable.page.PageProvider; 10 | 11 | /** 12 | * Created by dgautier on 3/18/2015. 13 | */ 14 | public class BaseTable extends Table { 15 | 16 | public BaseTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException { 17 | super(yStart, yStartNewPage, 0, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox())); 18 | } 19 | 20 | public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException { 21 | super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox())); 22 | } 23 | 24 | public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent, final PageProvider pageProvider) throws IOException { 25 | super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, pageProvider); 26 | } 27 | 28 | @Override 29 | protected void loadFonts() { 30 | // Do nothing as we don't have any fonts to load 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/AbstractTemplatedTable.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import java.io.IOException; 4 | 5 | import org.apache.pdfbox.pdmodel.PDDocument; 6 | 7 | import be.quodlibet.boxable.page.PageProvider; 8 | 9 | /** 10 | * Created by dgautier on 3/18/2015. 11 | */ 12 | public abstract class AbstractTemplatedTable extends Table { 13 | 14 | @Deprecated 15 | public AbstractTemplatedTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, T currentPage, boolean drawLines, boolean drawContent) throws IOException { 16 | super(yStart, yStartNewPage, bottomMargin, width, margin, document, currentPage, drawLines, drawContent); 17 | } 18 | 19 | @Deprecated 20 | public AbstractTemplatedTable(float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, boolean drawLines, boolean drawContent) throws IOException { 21 | super(yStartNewPage, bottomMargin, width, margin, document, drawLines, drawContent); 22 | setYStart(getCurrentPage().yStart()); 23 | } 24 | 25 | public AbstractTemplatedTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, T currentPage, boolean drawLines, boolean drawContent, PageProvider pageProvider) throws IOException { 26 | super(yStart, yStartNewPage, 0, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, pageProvider); 27 | } 28 | 29 | public AbstractTemplatedTable(float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, boolean drawLines, boolean drawContent, PageProvider pageProvider) throws IOException { 30 | super(yStartNewPage, 0, bottomMargin, width, margin, document, drawLines, drawContent, pageProvider); 31 | setYStart(getCurrentPage().yStart()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/text/Token.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | import org.apache.pdfbox.pdmodel.font.PDFont; 4 | 5 | import java.io.IOException; 6 | import java.util.Objects; 7 | 8 | // Token itself is thread safe, so you can reuse shared instances; 9 | // however, subclasses may have additional methods which are not thread safe. 10 | public class Token { 11 | 12 | private final TokenType type; 13 | 14 | private final String data; 15 | 16 | public Token(TokenType type, String data) { 17 | this.type = type; 18 | this.data = data; 19 | } 20 | 21 | public String getData() { 22 | return data; 23 | } 24 | 25 | public TokenType getType() { 26 | return type; 27 | } 28 | 29 | public float getWidth(PDFont font) throws IOException { 30 | return font.getStringWidth(getData()); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return getClass().getSimpleName() + "[" + type + "/" + data + "]"; 36 | } 37 | 38 | @Override 39 | public boolean equals(Object o) { 40 | if (this == o) return true; 41 | if (o == null || getClass() != o.getClass()) return false; 42 | Token token = (Token) o; 43 | return getType() == token.getType() && 44 | Objects.equals(getData(), token.getData()); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(getType(), getData()); 50 | } 51 | 52 | // Returns non-thread safe instance optimized for renderable text 53 | public static Token text(TokenType type, String data) { 54 | return new TextToken(type, data); 55 | } 56 | } 57 | 58 | // Non-thread safe subclass with caching to optimize tokens containing renderable text 59 | class TextToken extends Token { 60 | private PDFont cachedWidthFont; 61 | private float cachedWidth; 62 | 63 | TextToken(TokenType type, String data) { 64 | super(type, data); 65 | } 66 | 67 | @Override 68 | public float getWidth(PDFont font) throws IOException { 69 | if (font == cachedWidthFont) { 70 | return cachedWidth; 71 | } 72 | cachedWidth = super.getWidth(font); 73 | // must come after super call, in case it throws 74 | cachedWidthFont = font; 75 | return cachedWidth; 76 | } 77 | } -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/utils/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.utils; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.image.BufferedImage; 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import be.quodlibet.boxable.image.Image; 11 | 12 | /** 13 | *

14 | * Utility methods for images 15 | *

16 | * 17 | * @author mkuehne 18 | * @author hstimac 19 | */ 20 | public class ImageUtils { 21 | 22 | // utility class, no instance needed 23 | private ImageUtils() { 24 | } 25 | 26 | /** 27 | *

28 | * Simple reading image from file 29 | *

30 | * 31 | * @param imageFile 32 | * {@link File} from which image will be loaded 33 | * @return {@link Image} 34 | * @throws IOException if loading image fails 35 | */ 36 | public static Image readImage(File imageFile) throws IOException { 37 | final BufferedImage bufferedImage = ImageIO.read(imageFile); 38 | return new Image(bufferedImage); 39 | } 40 | 41 | /** 42 | *

43 | * Provide an ability to scale {@link Image} on desired {@link Dimension} 44 | *

45 | * 46 | * @param imageWidth Original image width 47 | * @param imageHeight Original image height 48 | * @param boundWidth Desired image width 49 | * @param boundHeight Desired image height 50 | * @return {@code Array} with image dimension. First value is width and second is height. 51 | */ 52 | public static float[] getScaledDimension(float imageWidth, float imageHeight, float boundWidth, float boundHeight) { 53 | float newImageWidth = imageWidth; 54 | float newImageHeight = imageHeight; 55 | 56 | // first check if we need to scale width 57 | if (imageWidth > boundWidth) { 58 | newImageWidth = boundWidth; 59 | // scale height to maintain aspect ratio 60 | newImageHeight = (newImageWidth * imageHeight) / imageWidth; 61 | } 62 | 63 | // then check if the new height is also bigger than expected 64 | if (newImageHeight > boundHeight) { 65 | newImageHeight = boundHeight; 66 | // scale width to maintain aspect ratio 67 | newImageWidth = (newImageHeight * imageWidth) / imageHeight; 68 | } 69 | 70 | float[] imageDimension = { newImageWidth, newImageHeight }; 71 | return imageDimension; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/line/LineStyle.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.line; 2 | 3 | import java.awt.BasicStroke; 4 | import java.awt.Color; 5 | import java.util.Objects; 6 | 7 | /** 8 | *

9 | * The LineStyle class defines a basic set of rendering attributes 10 | * for lines. 11 | *

12 | * 13 | * @author hstimac 14 | * @author mkuehne 15 | * 16 | */ 17 | public class LineStyle { 18 | 19 | private final Color color; 20 | 21 | private final float width; 22 | 23 | private float[] dashArray; 24 | 25 | private float dashPhase; 26 | 27 | /** 28 | *

29 | * Simple constructor for setting line {@link Color} and line width 30 | *

31 | * 32 | * @param color 33 | * The line {@link Color} 34 | * @param width 35 | * The line width 36 | */ 37 | public LineStyle(final Color color, final float width) { 38 | this.color = color; 39 | this.width = width; 40 | } 41 | 42 | /** 43 | *

44 | * Provides ability to produce dotted line. 45 | *

46 | * 47 | * @param color 48 | * The {@link Color} of the line 49 | * @param width 50 | * The line width 51 | * @return new styled line 52 | */ 53 | public static LineStyle produceDotted(final Color color, final int width) { 54 | final LineStyle line = new LineStyle(color, width); 55 | line.dashArray = new float[] { 1.0f }; 56 | line.dashPhase = 0.0f; 57 | 58 | return line; 59 | } 60 | 61 | /** 62 | *

63 | * Provides ability to produce dashed line. 64 | *

65 | * 66 | * @param color 67 | * The {@link Color} of the line 68 | * @param width 69 | * The line width 70 | * @return new styled line 71 | */ 72 | public static LineStyle produceDashed(final Color color, final int width) { 73 | return produceDashed(color, width, new float[] { 5.0f }, 0.0f); 74 | } 75 | 76 | /** 77 | * 78 | * @param color 79 | * The {@link Color} of the line 80 | * @param width 81 | * The line width 82 | * @param dashArray 83 | * Mimics the behavior of {@link BasicStroke#getDashArray()} 84 | * @param dashPhase 85 | * Mimics the behavior of {@link BasicStroke#getDashPhase()} 86 | * @return new styled line 87 | */ 88 | public static LineStyle produceDashed(final Color color, final int width, final float[] dashArray, 89 | final float dashPhase) { 90 | final LineStyle line = new LineStyle(color, width); 91 | line.dashArray = dashArray; 92 | line.dashPhase = dashPhase; 93 | 94 | return line; 95 | } 96 | 97 | public Color getColor() { 98 | return color; 99 | } 100 | 101 | public float getWidth() { 102 | return width; 103 | } 104 | 105 | public float[] getDashArray() { 106 | return dashArray; 107 | } 108 | 109 | public float getDashPhase() { 110 | return dashPhase; 111 | } 112 | 113 | @Override 114 | public int hashCode() 115 | { 116 | int hash = 7; 117 | hash = 89 * hash + Objects.hashCode(this.color); 118 | hash = 89 * hash + Float.floatToIntBits(this.width); 119 | return hash; 120 | } 121 | 122 | @Override 123 | public boolean equals(Object obj) 124 | { 125 | if (obj == null) { 126 | return false; 127 | } 128 | if (getClass() != obj.getClass()) { 129 | return false; 130 | } 131 | final LineStyle other = (LineStyle) obj; 132 | if (!Objects.equals(this.color, other.color)) { 133 | return false; 134 | } 135 | if (Float.floatToIntBits(this.width) != Float.floatToIntBits(other.width)) { 136 | return false; 137 | } 138 | return true; 139 | } 140 | 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/resources/fonts/INSTALL: -------------------------------------------------------------------------------- 1 | Installing GNU FreeFont 2 | ======================= 3 | 4 | GNU FreeFont can be used in any modern operating system. 5 | 6 | This document explains how to install FreeFont on some common systems. 7 | 8 | UNIX/GNU/Linux/BSD Systems 9 | -------------------------- 10 | 11 | FreeFont works with any system using the free font rasterizer FreeType 12 | . Some features such as glyph substitution and 13 | positioning may be handled by the text layout library 14 | Pango . 15 | 16 | Most recent systems using FreeType2 and Pango handle OpenType fonts well, 17 | but on older systems TrueType may perform better. 18 | 19 | * Debian GNU/Linux 20 | 21 | Users of Debian GNU/Linux system will probably want to use the Debian package, 22 | named 'ttf-freefont', available from the Debian Linux site. 23 | 24 | Install the fonts by issuing the command 25 | apt-get install ttf-freefont 26 | 27 | 28 | * KDE local installation 29 | 30 | Users of KDE can install .ttf files on a per-user basis using the KDE 31 | Control Center module "kcmfontinst", which may appear in the menu as 32 | 33 | Settings -> System Administration -> Font Installer 34 | 35 | This is especially helpful for developers and testers. 36 | 37 | 38 | * Generic X Window systems 39 | 40 | 1) Fetch the freefont-ttf.tar.gz package with Free UCS outline fonts 41 | in the TrueType format. 42 | 43 | 2) Unpack TrueType fonts into a suitable directory, 44 | e.g. /usr/local/share/fonts/default/TrueType/ 45 | 46 | 3) If you have chosen any other directory, make sure the directory you 47 | used to install the fonts is listed in the path searched by the X 48 | Font Server by editing the config file in /etc/X11/. 49 | 50 | In some systems, you list the directory in the item "catalogue=" 51 | in the file /etc/X11/fs/config. 52 | 53 | 4) Run ttmkfdir in the directory where you unpacked the fonts. 54 | 55 | 56 | Microsoft Windows 95/98/NT/2000/XP; Vista/7 57 | ------------------------------------------- 58 | 59 | Note that in at least Windows 7, Vista, XP and 2000, the TrueType versions 60 | perform much better than, and are recommended over, the OpenType ones. 61 | 62 | For good font smoothing in Windows, Microsoft ClearType must be enabled. 63 | The native Windows web browser must be used to install, enable, and configure 64 | ClearType. A web search for "ClearType Tuner" will find the proper web pages. 65 | Recent versions of the browser raise a security block (a yellow bar at the 66 | top of the window), which you must act upon to allow installation. A 67 | checkbox in the window turns ClearType on (in Win-speek, "Turn on ClearType"). 68 | The change happens immediately. 69 | 70 | * Vista, Windows 7: 71 | 1) From the Start menu, open Control Panels 72 | 2) Drag-n-drop font files onto Fonts control panel 73 | You may get a dialog saying 74 | "Windows needs your permission to continue" 75 | a) Click Continue 76 | 77 | * 95/98/NT: 78 | The font installation is similar to Vista. 79 | 80 | In order to use OpenType, users of Windows 95, 98 and NT 4.0 can 81 | install Adobe's 'Type Manager Light', which may be obtained from 82 | the Adobe web site. 83 | 84 | Otherwise, use the TrueType versions. 85 | 86 | Apple Mac OS X 87 | -------------- 88 | 89 | Support for OpenType on MacOS X started with OS 10.4, and has been improved 90 | gradually in later versions. 91 | 92 | Installing on Mac OS X consists of moving the font files to either 93 | /Library/Fonts/ or ~/Library/Fonts/ 94 | depending on whether they should be available to all users on your system 95 | or just to your own user. 96 | 97 | -------------------------------------------------------------------------- 98 | $Id: INSTALL,v 1.11 2011-06-12 07:14:12 Stevan_White Exp $ 99 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/text/PipelineLayer.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.regex.Pattern; 7 | 8 | import org.apache.pdfbox.pdmodel.font.PDFont; 9 | 10 | /** 11 | * 12 | * @author Markus Kühne 13 | * 14 | */ 15 | 16 | public class PipelineLayer { 17 | 18 | private static String rtrim(String s) { 19 | int len = s.length(); 20 | while ((len > 0) && (s.charAt(len - 1) <= ' ')) { 21 | len--; 22 | } 23 | if (len == s.length()) { 24 | return s; 25 | } 26 | if (len == 0) { 27 | return ""; 28 | } 29 | return s.substring(0, len); 30 | } 31 | 32 | private final StringBuilder text = new StringBuilder(); 33 | 34 | private String lastTextToken = ""; 35 | 36 | private List tokens = new ArrayList<>(); 37 | 38 | private String trimmedLastTextToken = ""; 39 | 40 | private float width; 41 | 42 | private float widthLastToken; 43 | 44 | private float widthTrimmedLastToken; 45 | 46 | private float widthCurrentText; 47 | 48 | public boolean isEmpty() { 49 | return tokens.isEmpty(); 50 | } 51 | 52 | public void push(final Token token) { 53 | tokens.add(token); 54 | } 55 | 56 | public void push(final PDFont font, final float fontSize, final Token token) throws IOException { 57 | if (token.getType().equals(TokenType.PADDING)) { 58 | width += Float.parseFloat(token.getData()); 59 | } 60 | if (token.getType().equals(TokenType.BULLET)) { 61 | // just appending one space because our bullet width will be wide as one character of current font 62 | text.append(token.getData()); 63 | width += (token.getWidth(font) / 1000f * fontSize); 64 | } 65 | 66 | if (token.getType().equals(TokenType.ORDERING)) { 67 | // just appending one space because our bullet width will be wide as one character of current font 68 | text.append(token.getData()); 69 | width += (token.getWidth(font) / 1000f * fontSize); 70 | } 71 | 72 | if (token.getType().equals(TokenType.TEXT)) { 73 | text.append(lastTextToken); 74 | width += widthLastToken; 75 | lastTextToken = token.getData(); 76 | trimmedLastTextToken = rtrim(lastTextToken); 77 | widthLastToken = token.getWidth(font) / 1000f * fontSize; 78 | 79 | if (trimmedLastTextToken.length() == lastTextToken.length()) { 80 | widthTrimmedLastToken = widthLastToken; 81 | } else { 82 | widthTrimmedLastToken = (font.getStringWidth(trimmedLastTextToken) / 1000f * fontSize); 83 | } 84 | 85 | widthCurrentText = text.length() == 0 ? 0 : 86 | (font.getStringWidth(text.toString()) / 1000f * fontSize); 87 | } 88 | 89 | push(token); 90 | } 91 | 92 | public void push(final PipelineLayer pipeline) { 93 | text.append(lastTextToken); 94 | width += widthLastToken; 95 | text.append(pipeline.text); 96 | if (pipeline.text.length() > 0) { 97 | width += pipeline.widthCurrentText; 98 | } 99 | lastTextToken = pipeline.lastTextToken; 100 | trimmedLastTextToken = pipeline.trimmedLastTextToken; 101 | widthLastToken = pipeline.widthLastToken; 102 | widthTrimmedLastToken = pipeline.widthTrimmedLastToken; 103 | tokens.addAll(pipeline.tokens); 104 | 105 | pipeline.reset(); 106 | } 107 | 108 | public void reset() { 109 | text.delete(0, text.length()); 110 | width = 0.0f; 111 | lastTextToken = ""; 112 | trimmedLastTextToken = ""; 113 | widthLastToken = 0.0f; 114 | widthTrimmedLastToken = 0.0f; 115 | tokens.clear(); 116 | } 117 | 118 | public String trimmedText() { 119 | return text.toString() + trimmedLastTextToken; 120 | } 121 | 122 | public float width() { 123 | return width + widthLastToken; 124 | } 125 | 126 | public float trimmedWidth() { 127 | return width + widthTrimmedLastToken; 128 | } 129 | 130 | public List tokens() { 131 | return new ArrayList<>(tokens); 132 | } 133 | 134 | @Override 135 | public String toString() { 136 | return text.toString() + "(" + lastTextToken + ") [width: " + width() + ", trimmed: " + trimmedWidth() + "]"; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/utils/PDStreamUtils.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.utils; 2 | 3 | import java.awt.Color; 4 | import java.io.IOException; 5 | 6 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 7 | import org.apache.pdfbox.pdmodel.font.PDFont; 8 | 9 | import be.quodlibet.boxable.line.LineStyle; 10 | 11 | /** 12 | *

13 | * Utility methods for {@link PDPageContentStream} 14 | *

15 | * 16 | * @author hstimac 17 | * @author mkuehne 18 | * 19 | */ 20 | public final class PDStreamUtils { 21 | 22 | private PDStreamUtils() { 23 | } 24 | 25 | /** 26 | *

27 | * Provides ability to write on a {@link PDPageContentStream}. The text will 28 | * be written above Y coordinate. 29 | *

30 | * 31 | * @param stream 32 | * The {@link PDPageContentStream} where writing will be applied. 33 | * @param text 34 | * The text which will be displayed. 35 | * @param font 36 | * The font of the text 37 | * @param fontSize 38 | * The font size of the text 39 | * @param x 40 | * Start X coordinate for text. 41 | * @param y 42 | * Start Y coordinate for text. 43 | * @param color 44 | * Color of the text 45 | */ 46 | public static void write(final PageContentStreamOptimized stream, final String text, final PDFont font, 47 | final float fontSize, final float x, final float y, final Color color) { 48 | try { 49 | stream.setFont(font, fontSize); 50 | // we want to position our text on his baseline 51 | stream.newLineAt(x, y - FontUtils.getDescent(font, fontSize) - FontUtils.getHeight(font, fontSize)); 52 | stream.setNonStrokingColor(color); 53 | stream.showText(text); 54 | } catch (final IOException e) { 55 | throw new IllegalStateException("Unable to write text", e); 56 | } 57 | } 58 | 59 | /** 60 | *

61 | * Provides ability to draw rectangle for debugging purposes. 62 | *

63 | * 64 | * @param stream 65 | * The {@link PDPageContentStream} where drawing will be applied. 66 | * @param x 67 | * Start X coordinate for rectangle. 68 | * @param y 69 | * Start Y coordinate for rectangle. 70 | * @param width 71 | * Width of rectangle 72 | * @param height 73 | * Height of rectangle 74 | * @param color 75 | * Color of the text 76 | */ 77 | public static void rect(final PageContentStreamOptimized stream, final float x, final float y, final float width, 78 | final float height, final Color color) { 79 | try { 80 | stream.setNonStrokingColor(color); 81 | // negative height because we want to draw down (not up!) 82 | stream.addRect(x, y, width, -height); 83 | stream.fill(); 84 | } catch (final IOException e) { 85 | throw new IllegalStateException("Unable to draw rectangle", e); 86 | } 87 | } 88 | 89 | /** 90 | *

91 | * Provides ability to draw font metrics (font height, font ascent, font 92 | * descent). 93 | *

94 | * 95 | * @param stream 96 | * The {@link PDPageContentStream} where drawing will be applied. 97 | * @param x 98 | * Start X coordinate for rectangle. 99 | * @param y 100 | * Start Y coordinate for rectangle. 101 | * @param font 102 | * {@link PDFont} from which will be obtained font metrics 103 | * @param fontSize 104 | * Font size 105 | */ 106 | public static void rectFontMetrics(final PageContentStreamOptimized stream, final float x, final float y, 107 | final PDFont font, final float fontSize) { 108 | // height 109 | PDStreamUtils.rect(stream, x, y, 3, FontUtils.getHeight(font, fontSize), Color.BLUE); 110 | // ascent 111 | PDStreamUtils.rect(stream, x + 3, y, 3, FontUtils.getAscent(font, fontSize), Color.CYAN); 112 | // descent 113 | PDStreamUtils.rect(stream, x + 3, y - FontUtils.getHeight(font, fontSize), 3, FontUtils.getDescent(font, 14), 114 | Color.GREEN); 115 | } 116 | 117 | /** 118 | *

119 | * Provides ability to set different line styles (line width, dotted line, 120 | * dashed line) 121 | *

122 | * 123 | * @param stream 124 | * The {@link PDPageContentStream} where drawing will be applied. 125 | * @param line 126 | * The {@link LineStyle} that would be applied 127 | * @throws IOException If the content stream could not be written or the line color cannot be retrieved. 128 | */ 129 | public static void setLineStyles(final PageContentStreamOptimized stream, final LineStyle line) throws IOException { 130 | stream.setStrokingColor(line.getColor()); 131 | stream.setLineWidth(line.getWidth()); 132 | stream.setLineCapStyle(0); 133 | if (line.getDashArray() != null) { 134 | stream.setLineDashPattern(line.getDashArray(), line.getDashPhase()); 135 | } else { 136 | stream.setLineDashPattern(new float[] {}, 0.0f); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/resources/fonts/TROUBLESHOOTING: -------------------------------------------------------------------------------- 1 | Troubleshooting GNU FreeFont 2 | 3 | So your text looks lousy, although you installed FreeFont and you seem to be 4 | using it. What do you do? 5 | 6 | Before you blame the problem on FreeFont, take the time to double-check that 7 | the text you are looking at is really rendered with FreeFont. 8 | 9 | Be aware that not all Unicode characters are supported by FreeFont, and 10 | even characters supported by one face, such as Serif, might not be 11 | supported by other faces such as Sans. 12 | 13 | Also, some systems have settings that strongly affect the rendering 14 | of fonts. It may be worth tweaking these. 15 | 16 | glyph substitution 17 | ================== 18 | 19 | When given the task of displaying characters in text, modern font rendering 20 | software usually tries to display *something*, even if the font it is 21 | *supposed* to be using does not contain glyphs for all the characters in the 22 | text. The software will snoop through all the fonts on the system to find 23 | one that has a glyph for the one missing in the desired font. So although 24 | you have specified FreeSans-bold, you may be looking at a letter from quite 25 | a different font. 26 | 27 | First double-check that the font in question really contains the character 28 | in question. If you don't have font development software, this can be 29 | tricky. In the case of FreeFont, you can check if a given character 30 | range is supported: 31 | 32 | Next double-check that your application (web browser, text editor, etc) 33 | has indeed been properly instructed to use the font. 34 | 35 | Then double-check that the font is really installed in the system. 36 | (This depends on the operating system, of course.) 37 | 38 | Linux and Unix 39 | ============== 40 | 41 | Modern Linux systems use a system called fontconfig, which maintains a font 42 | cache, for efficiency. 43 | 44 | The font cache can really complicate font installation and troubleshooting 45 | however. It can happen that when a font is newly installed, what is 46 | displayed is coming out of an old cache entry rather than the new font. 47 | 48 | Just what to do depends on how and where the font was installed. 49 | 50 | Fonts installed system-wide are usually put in a directory such as 51 | /usr/share/fonts/ 52 | the font cache for these might be in 53 | /var/cache/fontconfig/ 54 | Fonts installed just for one user account will typically be in 55 | ~/.fonts/ 56 | and the cache will be 57 | ~/.fontconfig/ 58 | 59 | You can clean your local cache merely by emptying the directory 60 | ~/.fontconfig/ 61 | In any case, to clean the cache, you can use the fontconfig command 62 | fc-cache -vf 63 | If run as root, it will clean the system cache, if run as a normal user, 64 | it cleans only the normal user's cache. 65 | 66 | The procedure for local fonts is: 67 | 1) shut off any program using the fonts in question 68 | 2) clean the cache 69 | 3) re-start the program 70 | The procedure for system-wide fonts is: 71 | 1) log out of the X Windows session 72 | 2) in a console, clean the cache 73 | 3) log in to an X Windows session 74 | 75 | LibreOffice / OpenOffice 76 | ======================== 77 | These products have their own font rendering libraries, which have 78 | idiosyncratic behavior. 79 | 80 | It has recently been reported that as of LibreOffice 3.5.1, font features 81 | are disabled for OpenType fonts. If you use FreeFont with these products, 82 | you may want to install the TrueType versions of the fonts. 83 | 84 | Windows 85 | ======= 86 | 87 | The most common complaint has to do with "blurry text". There are two 88 | causes. 89 | 90 | The first is that ClearType smoothing is turned off. The best way to check 91 | is to use the native Windows Web browser. Do a search for "ClearType Tuner". 92 | The Microsoft pages install a tuner for ClearType. A security block notice 93 | will appear at the top of the window--you have to allow the installation. 94 | Then check the box "Turn on ClearType". The change happens immediately. 95 | 96 | The secont cause is that the FreeFont version with cubic spline outlines is 97 | installed. As of the 2012 GNU FreeFont release, the TrueType builds have 98 | quadratic splines, which work best with Windows' rendering software. 99 | TTF (TrueType) quadratic splines Windows 7, Vista, Windows XP. 100 | OTF (OpenType) cubic splines Linux, Mac 101 | 102 | Note also: Firefox has a setting for ClearType: 103 | gfx.font_rendering.cleartype_params.rendering_mode 104 | A value of 2 sets it to old-style GDI rendering, while -1 is the default. 105 | 106 | reporting problems 107 | ================== 108 | 109 | If you really think you're seeing a bug in FreeFont, or if you have 110 | a suggestion, consider opening a problem report at 111 | https://savannah.gnu.org/bugs/?group=freefont 112 | It is best that you make a Savannah account and log in with that, so 113 | you can be e-mailed whenever changes are made to your report. 114 | 115 | $Id: troubleshooting.txt,v 1.10 2011-07-16 08:38:06 Stevan_White Exp $ 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Boxable](http://dhorions.github.io/boxable/) - A java library to build tables in PDF documents. 2 | ======= 3 | 4 | [![Join the chat at https://gitter.im/dhorions/boxable](https://badges.gitter.im/dhorions/boxable.svg)](https://gitter.im/dhorions/boxable?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | [![Build Status](https://travis-ci.org/dhorions/boxable.svg?branch=master)](https://travis-ci.org/dhorions/boxable) 6 | [![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5UL3NVLA852MN&source=url) 7 | 8 | Boxable is a library that can be used to easily create tables in pdf documents. It uses the [PDFBox](https://pdfbox.apache.org/) PDF library under the hood. 9 | 10 | # Features 11 | 12 | - Build tables in pdf documents 13 | - Convert csv data into tables in pdf documents 14 | - Convert Lists into tables in pdf documents 15 | 16 | #### Boxable supports next tables features 17 | - HTML tags in cell content (not all! `

,,,
,

    ,
      ,
    1. `) 18 | - Horizontal & Vertical Alignment of the text 19 | - Images inside cells and outside table (image scale is also supported) 20 | - basic set of rendering attributes for lines (borders) 21 | - rotated text (by 90 degrees) 22 | - writing text outside tables 23 | 24 | # Maven 25 | ```xml 26 | 27 | com.github.dhorions 28 | boxable 29 | 1.7.0 30 | 31 | ``` 32 | For other build systems, check the [Maven Central Repository](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22boxable%22). 33 | 34 | 35 | # Tutorial 36 | 37 | A tutorial is being created and will be accessible at https://github.com/dhorions/boxable/wiki. 38 | If you want to help, please let us know [here](https://github.com/dhorions/boxable/issues/41). 39 | 40 | # Usage examples 41 | 42 | ## Create a pdf from a csv file 43 | 44 | ```java 45 | String data = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/Eurostat_Immigration_Applications.csv"); 46 | BaseTable pdfTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true,true); 47 | DataTable t = new DataTable(pdfTable, page); 48 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 49 | pdfTable.draw(); 50 | ``` 51 | Output : [CSVExamplePortrait.pdf](https://s3.amazonaws.com/misc.quodlibet.be/Boxable/CSVexamplePortrait.pdf) 52 | 53 | ## Create a pdf from a List 54 | 55 | ```java 56 | List data = new ArrayList(); 57 | data.add(new ArrayList<>( 58 | Arrays.asList("Column One", "Column Two", "Column Three", "Column Four", "Column Five"))); 59 | for (int i = 1; i <= 100; i++) { 60 | data.add(new ArrayList<>( 61 | Arrays.asList("Row " + i + " Col One", "Row " + i + " Col Two", "Row " + i + " Col Three", "Row " + i + " Col Four", "Row " + i + " Col Five"))); 62 | } 63 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, true); 64 | DataTable t = new DataTable(dataTable, page); 65 | t.addListToTable(data, DataTable.HASHEADER); 66 | dataTable.draw(); 67 | ``` 68 | Output : [ListExampleLandscape.pdf](https://s3.amazonaws.com/misc.quodlibet.be/Boxable/ListExampleLandscape.pdf) 69 | 70 | ## Build tables in pdf documents 71 | 72 | ```java 73 | BaseTable table = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 74 | drawContent); 75 | //Create Header row 76 | Row headerRow = table.createRow(15f); 77 | Cell cell = headerRow.createCell(100, "Awesome Facts About Belgium"); 78 | cell.setFont(PDType1Font.HELVETICA_BOLD); 79 | cell.setFillColor(Color.BLACK); 80 | table.addHeaderRow(headerRow); 81 | List facts = getFacts(); 82 | for (String[] fact : facts) { 83 | Row row = table.createRow(10f); 84 | cell = row.createCell((100 / 3.0f) * 2, fact[0] ); 85 | for (int i = 1; i < fact.length; i++) { 86 | cell = row.createCell((100 / 9f), fact[i]); 87 | } 88 | } 89 | table.draw(); 90 | ``` 91 | 92 | Special Thanks to these awesome contributers : 93 | - [@johnmanko](https://github.com/johnmanko) 94 | - [@Vobarian](https://github.com/vobarian) 95 | - [@Giboow](https://github.com/giboow) 96 | - [@Ogmios-Voice](https://github.com/ogmios-voice) 97 | - [@zaqpiotr](https://github.com/zaqpiotr) 98 | - [Frulenzo](https://github.com/Frulenzo) 99 | - [dgautier](https://github.com/dgautier) 100 | - [ZeerDonker](https://github.com/ZeerDonker) 101 | - [dobluth](https://github.com/dobluth) 102 | - [schmitzhermes](https://github.com/schmitzhermes) 103 | 104 | ======= 105 | 106 | Copyright [2022](Quodlibet.be) 107 | 108 | Licensed under the Apache License, Version 2.0 (the "License"); 109 | you may not use this file except in compliance with the License. 110 | You may obtain a copy of the License at 111 | 112 | http://www.apache.org/licenses/LICENSE-2.0 113 | 114 | Unless required by applicable law or agreed to in writing, software 115 | distributed under the License is distributed on an "AS IS" BASIS, 116 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 117 | See the License for the specific language governing permissions and 118 | limitations under the License. 119 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/image/Image.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.image; 2 | 3 | import be.quodlibet.boxable.utils.ImageUtils; 4 | import be.quodlibet.boxable.utils.PageContentStreamOptimized; 5 | import java.awt.image.BufferedImage; 6 | import java.io.IOException; 7 | import org.apache.pdfbox.pdmodel.PDDocument; 8 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 9 | import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory; 10 | import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; 11 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 12 | 13 | 14 | public class Image { 15 | 16 | private final BufferedImage image; 17 | 18 | private float width; 19 | 20 | private float height; 21 | 22 | private PDImageXObject imageXObject = null; 23 | 24 | // standard DPI 25 | private float[] dpi = { 72, 72 }; 26 | 27 | private float quality = 1f; 28 | 29 | /** 30 | *

      31 | * Constructor for default images 32 | *

      33 | * 34 | * @param image 35 | * {@link BufferedImage} 36 | */ 37 | public Image(final BufferedImage image) { 38 | this.image = image; 39 | this.width = image.getWidth(); 40 | this.height = image.getHeight(); 41 | } 42 | 43 | public Image(final BufferedImage image, float dpi) { 44 | this(image, dpi, dpi); 45 | } 46 | 47 | public Image(final BufferedImage image, float dpiX, float dpiY) { 48 | this.image = image; 49 | this.width = image.getWidth(); 50 | this.height = image.getHeight(); 51 | this.dpi[0] = dpiX; 52 | this.dpi[1] = dpiY; 53 | scaleImageFromPixelToPoints(); 54 | } 55 | 56 | /** 57 | *

      58 | * Drawing simple {@link Image} in {@link PDPageContentStream}. 59 | *

      60 | * 61 | * @param doc 62 | * {@link PDDocument} where drawing will be applied 63 | * @param stream 64 | * {@link PDPageContentStream} where drawing will be applied 65 | * @param x 66 | * X coordinate for image drawing 67 | * @param y 68 | * Y coordinate for image drawing 69 | * @throws IOException if loading image fails 70 | */ 71 | public void draw(final PDDocument doc, final PageContentStreamOptimized stream, float x, float y) throws IOException 72 | { 73 | if (imageXObject == null) { 74 | if(quality == 1f) { 75 | imageXObject = LosslessFactory.createFromImage(doc, image); 76 | } else { 77 | imageXObject = JPEGFactory.createFromImage(doc, image, quality); 78 | } 79 | } 80 | stream.drawImage(imageXObject, x, y - height, width, height); 81 | } 82 | 83 | /** 84 | *

      85 | * Method which scale {@link Image} with designated width 86 | *

      87 | * 88 | * @deprecated Use {@link #scaleByWidth} 89 | * @param width 90 | * Maximal height where {@link Image} needs to be scaled 91 | * @return Scaled {@link Image} 92 | */ 93 | public Image scale(float width) { 94 | return scaleByWidth(width); 95 | } 96 | 97 | /** 98 | *

      99 | * Method which scale {@link Image} with designated width 100 | *

      101 | * 102 | * @param width 103 | * Maximal width where {@link Image} needs to be scaled 104 | * @return Scaled {@link Image} 105 | */ 106 | public Image scaleByWidth(float width) { 107 | float factorWidth = width / this.width; 108 | return scale(width, this.height * factorWidth); 109 | } 110 | 111 | private void scaleImageFromPixelToPoints() { 112 | float dpiX = dpi[0]; 113 | float dpiY = dpi[1]; 114 | scale(getImageWidthInPoints(dpiX), getImageHeightInPoints(dpiY)); 115 | } 116 | 117 | /** 118 | *

      119 | * Method which scale {@link Image} with designated height 120 | * 121 | * @param height 122 | * Maximal height where {@link Image} needs to be scaled 123 | * @return Scaled {@link Image} 124 | */ 125 | public Image scaleByHeight(float height) { 126 | float factorHeight = height / this.height; 127 | return scale(this.width * factorHeight, height); 128 | } 129 | 130 | public float getImageWidthInPoints(float dpiX) { 131 | return this.width * 72f / dpiX; 132 | } 133 | 134 | public float getImageHeightInPoints(float dpiY) { 135 | return this.height * 72f / dpiY; 136 | } 137 | 138 | /** 139 | *

      140 | * Method which scale {@link Image} with designated width und height 141 | * 142 | * @param boundWidth 143 | * Maximal width where {@link Image} needs to be scaled 144 | * @param boundHeight 145 | * Maximal height where {@link Image} needs to be scaled 146 | * @return scaled {@link Image} 147 | */ 148 | public Image scale(float boundWidth, float boundHeight) { 149 | float[] imageDimension = ImageUtils.getScaledDimension(this.width, this.height, boundWidth, boundHeight); 150 | this.width = imageDimension[0]; 151 | this.height = imageDimension[1]; 152 | return this; 153 | } 154 | 155 | public float getHeight() { 156 | return height; 157 | } 158 | 159 | public float getWidth() { 160 | return width; 161 | } 162 | 163 | public void setQuality(float quality) throws IllegalArgumentException { 164 | if(quality <= 0 || quality > 1) { 165 | throw new IllegalArgumentException( 166 | "The quality value must be configured greater than zero and less than or equal to 1"); 167 | } 168 | this.quality = quality; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/main/resources/fonts/README: -------------------------------------------------------------------------------- 1 | -*-text-*- 2 | GNU FreeFont 3 | 4 | The GNU FreeFont project aims to provide a useful set of free scalable 5 | (i.e., OpenType) fonts covering as much as possible of the ISO 10646/Unicode 6 | UCS (Universal Character Set). 7 | 8 | Statement of Purpose 9 | -------------------- 10 | 11 | The practical reason for putting glyphs together in a single font face is 12 | to conveniently mix symbols and characters from different writing systems, 13 | without having to switch fonts. 14 | 15 | Coverage 16 | -------- 17 | 18 | FreeFont covers the following character ranges 19 | * Latin, Cyrillic, and Arabic, with supplements for many languages 20 | * Greek, Hebrew, Armenian, Georgian, Thaana, Syriac 21 | * Devanagari, Bengali, Gujarati, Gurmukhi, Sinhala, Tamil, Malayalam 22 | * Thai, Tai Le, Kayah Li, Hanunóo, Buginese 23 | * Cherokee, Unified Canadian Aboriginal Syllabics 24 | * Ethiopian, Tifnagh, Vai, Osmanya, Coptic 25 | * Glagolitic, Gothic, Runic, Ugaritic, Old Persian, Phoenician, Old Italic 26 | * Braille, International Phonetic Alphabet 27 | * currency symbols, general punctuation and diacritical marks, dingbats 28 | * mathematical symbols, including much of the TeX repertoire of symbols 29 | * technical symbols: APL, OCR, arrows, 30 | * geometrical shapes, box drawing 31 | * musical symbols, gaming symbols, miscellaneous symbols 32 | etc. 33 | For more detail see 34 | 35 | Editing 36 | ------- 37 | 38 | The free outline font editor, George Williams' FontForge 39 | is used for editing the fonts. 40 | 41 | Design Issues 42 | ------------- 43 | 44 | Which font shapes should be made? Historical style terms like Renaissance 45 | or Baroque letterforms cannot be applied beyond Latin/Cyrillic/Greek 46 | scripts to any greater extent than Kufi or Nashki can be applied beyond 47 | Arabic script; "italic" is strictly meaningful only for Latin letters, 48 | although many scripts such as Cyrillic have a history with "cursive" and 49 | many others with "oblique" faces. 50 | 51 | However, most modern writing systems have typographic formulations for 52 | contrasting uniform and modulated character stroke widths, and since the 53 | advent of the typewriter, most have developed a typographic style with 54 | uniform-width characters. 55 | 56 | Accordingly, the FreeFont family has one monospaced - FreeMono - and two 57 | proportional faces (one with uniform stroke - FreeSans - and one with 58 | modulated stroke - FreeSerif). 59 | 60 | The point of having characters from different writing systems in one font 61 | is that mixed text should look good, and so each FreeFont face contains 62 | characters of similar style and weight. 63 | 64 | Licensing 65 | --------- 66 | 67 | Free UCS scalable fonts is free software; you can redistribute it and/or 68 | modify it under the terms of the GNU General Public License as published 69 | by the Free Software Foundation; either version 3 of the License, or 70 | (at your option) any later version. 71 | 72 | The fonts are distributed in the hope that they will be useful, but 73 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 74 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 75 | for more details. 76 | 77 | You should have received a copy of the GNU General Public License along 78 | with this program; if not, write to the Free Software Foundation, Inc., 79 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 80 | 81 | As a special exception, if you create a document which uses this font, and 82 | embed this font or unaltered portions of this font into the document, this 83 | font does not by itself cause the resulting document to be covered by the 84 | GNU General Public License. This exception does not however invalidate any 85 | other reasons why the document might be covered by the GNU General Public 86 | License. If you modify this font, you may extend this exception to your 87 | version of the font, but you are not obligated to do so. If you do not 88 | wish to do so, delete this exception statement from your version. 89 | 90 | Files and their suffixes 91 | ------------------------ 92 | 93 | The files with .sfd (Spline Font Database) are in FontForge's native format. 94 | They may be used to modify the fonts. 95 | 96 | TrueType fonts are the files with the .ttf (TrueType Font) suffix. These 97 | are ready to use in Linux/Unix, on Apple Mac OS, and on Microsoft Windows 98 | systems. 99 | 100 | OpenType fonts (with suffix .otf) are preferred for use on Linux/Unix, 101 | but *not* for recent Microsoft Windows systems. 102 | See the INSTALL file for more information. 103 | 104 | Web Open Font Format files (with suffix .woff) are for use in Web sites. 105 | See the webfont_guidelines.txt for further information. 106 | 107 | Further information 108 | ------------------- 109 | 110 | Home page of GNU FreeFont: 111 | http://www.gnu.org/software/freefont/ 112 | 113 | More information is at the main project page of Free UCS scalable fonts: 114 | http://savannah.gnu.org/projects/freefont/ 115 | 116 | To report problems with GNU FreeFont, it is best to obtain a Savannah 117 | account and post reports using that account on 118 | https://savannah.gnu.org/bugs/ 119 | 120 | Public discussions about GNU FreeFont may be posted to the mailing list 121 | freefont-bugs@gnu.org 122 | 123 | -------------------------------------------------------------------------- 124 | Original author: Primoz Peterlin 125 | Current administrator: Steve White 126 | 127 | $Id: README,v 1.10 2011-06-12 07:14:12 Stevan_White Exp $ 128 | -------------------------------------------------------------------------------- /src/test/java/be/quodlibet/boxable/text/TokenizerTest.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | 10 | public class TokenizerTest { 11 | 12 | private WrappingFunction wrappingFunction = null; 13 | 14 | @Test 15 | public void testWrapPoints() throws Exception { 16 | final String text = "1 123 123456 12"; 17 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 18 | Assert.assertEquals(Arrays.asList( 19 | Token.text(TokenType.TEXT, "1 "), 20 | new Token(TokenType.POSSIBLE_WRAP_POINT, ""), 21 | Token.text(TokenType.TEXT, "123 "), 22 | new Token(TokenType.POSSIBLE_WRAP_POINT, ""), 23 | Token.text(TokenType.TEXT, "123456 "), 24 | new Token(TokenType.POSSIBLE_WRAP_POINT, ""), 25 | Token.text(TokenType.TEXT, "12"), 26 | new Token(TokenType.POSSIBLE_WRAP_POINT, "") 27 | ), tokens); 28 | } 29 | 30 | @Test 31 | public void testEndsWithLt() throws Exception { 32 | final String text = "1 123 123456 12<"; 33 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 34 | if (TokenType.CLOSE_TAG.equals(tokens.get(tokens.size()-1).getType())) { 35 | Assert.assertEquals("Text doesn't end with '<' character", "<", tokens.get(tokens.size()-1).getData()); 36 | } 37 | } 38 | 39 | @Test 40 | public void testSimpleItalic() throws Exception { 41 | { 42 | final String text = "1 123 123456 12"; 43 | final StringBuilder italicText = new StringBuilder(); 44 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 45 | boolean italic = false; 46 | for (final Token token : tokens) { 47 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("i")) { 48 | italic = true; 49 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("i")){ 50 | italic = false; 51 | } 52 | if(TokenType.TEXT.equals(token.getType()) && italic){ 53 | italicText.append(token.getData()); 54 | } 55 | } 56 | Assert.assertEquals("Italic text is parsed wrong", "123 123456", italicText.toString()); 57 | } 58 | { 59 | final String text = "1 123 123456 12"; 60 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 61 | final StringBuilder italicText = new StringBuilder(); 62 | boolean italic = false; 63 | for (final Token token : tokens) { 64 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("i")) { 65 | italic = true; 66 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("i")){ 67 | italic = false; 68 | } 69 | if(TokenType.TEXT.equals(token.getType()) && italic){ 70 | italicText.append(token.getData()); 71 | } 72 | } 73 | Assert.assertEquals("Italic text is parsed wrong", "123 123456", italicText.toString()); 74 | } 75 | } 76 | 77 | @Test 78 | public void testBoldAndItalic() throws Exception { 79 | { 80 | final String text = "1 123 123456 12"; 81 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 82 | final StringBuilder boldItalicText = new StringBuilder(); 83 | boolean bold = false; 84 | boolean italic = false; 85 | for (final Token token : tokens) { 86 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("b")) { 87 | bold = true; 88 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("b")){ 89 | bold = false; 90 | } 91 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("i")) { 92 | italic = true; 93 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("i")){ 94 | italic = false; 95 | } 96 | 97 | if(TokenType.TEXT.equals(token.getType()) && bold && italic){ 98 | boldItalicText.append(token.getData()); 99 | } 100 | } 101 | Assert.assertEquals("Bold-italic text is parsed wrong", "123", boldItalicText.toString()); 102 | } 103 | { 104 | final String text = "1 123 123456 12"; 105 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 106 | final StringBuilder boldItalicText = new StringBuilder(); 107 | boolean bold = false; 108 | boolean italic = false; 109 | for (final Token token : tokens) { 110 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("b")) { 111 | bold = true; 112 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("b")){ 113 | bold = false; 114 | } 115 | if (TokenType.OPEN_TAG.equals(token.getType()) && token.getData().equals("i")) { 116 | italic = true; 117 | } else if(TokenType.CLOSE_TAG.equals(token.getType()) && token.getData().equals("i")){ 118 | italic = false; 119 | } 120 | 121 | if(TokenType.TEXT.equals(token.getType()) && bold && italic){ 122 | boldItalicText.append(token.getData()); 123 | } 124 | } 125 | Assert.assertEquals("Bold-italic text is parsed wrong", "123456", boldItalicText.toString()); 126 | } 127 | } 128 | 129 | @Test 130 | public void testEmptyString() throws Exception { 131 | // "" 132 | { 133 | final String text = ""; 134 | final List tokens = Tokenizer.tokenize(text, wrappingFunction); 135 | for (final Token token : tokens) { 136 | if (TokenType.TEXT.equals(token.getType()) && token.getData().equals("")) { 137 | Assert.assertEquals("Bold-italic text is parsed wrong", "", token.getData()); 138 | } 139 | } 140 | } 141 | // null 142 | { 143 | final String textNull = null; 144 | final List tokens = Tokenizer.tokenize(textNull, wrappingFunction); 145 | Assert.assertEquals("Bold-italic text is parsed wrong", Collections.emptyList(), tokens); 146 | } 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/utils/PageContentStreamOptimized.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.utils; 2 | 3 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 4 | import org.apache.pdfbox.pdmodel.font.PDFont; 5 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; 6 | import org.apache.pdfbox.util.Matrix; 7 | 8 | import java.awt.Color; 9 | import java.io.IOException; 10 | import java.util.Arrays; 11 | 12 | public class PageContentStreamOptimized { 13 | private static final Matrix ROTATION = Matrix.getRotateInstance(Math.PI * 0.5, 0, 0); 14 | 15 | private final PDPageContentStream pageContentStream; 16 | private boolean textMode; 17 | private float textCursorAbsoluteX; 18 | private float textCursorAbsoluteY; 19 | private boolean rotated; 20 | 21 | public PageContentStreamOptimized(PDPageContentStream pageContentStream) { 22 | this.pageContentStream = pageContentStream; 23 | } 24 | 25 | public void setRotated(boolean rotated) throws IOException { 26 | if (this.rotated == rotated) return; 27 | if (rotated) { 28 | if (textMode) { 29 | pageContentStream.setTextMatrix(ROTATION); 30 | textCursorAbsoluteX = 0; 31 | textCursorAbsoluteY = 0; 32 | } 33 | } else { 34 | endText(); 35 | } 36 | this.rotated = rotated; 37 | } 38 | 39 | public void beginText() throws IOException { 40 | if (!textMode) { 41 | pageContentStream.beginText(); 42 | if (rotated) { 43 | pageContentStream.setTextMatrix(ROTATION); 44 | } 45 | textMode = true; 46 | textCursorAbsoluteX = 0; 47 | textCursorAbsoluteY = 0; 48 | } 49 | } 50 | 51 | public void endText() throws IOException { 52 | if (textMode) { 53 | pageContentStream.endText(); 54 | textMode = false; 55 | } 56 | } 57 | 58 | private PDFont currentFont; 59 | private float currentFontSize; 60 | 61 | public void setFont(PDFont font, float fontSize) throws IOException { 62 | if (font != currentFont || fontSize != currentFontSize) { 63 | pageContentStream.setFont(font, fontSize); 64 | currentFont = font; 65 | currentFontSize = fontSize; 66 | } 67 | } 68 | 69 | public void showText(String text) throws IOException { 70 | beginText(); 71 | pageContentStream.showText(text); 72 | } 73 | 74 | public void newLineAt(float tx, float ty) throws IOException { 75 | beginText(); 76 | float dx = tx - textCursorAbsoluteX; 77 | float dy = ty - textCursorAbsoluteY; 78 | if (rotated) { 79 | pageContentStream.newLineAtOffset(dy, -dx); 80 | } else { 81 | pageContentStream.newLineAtOffset(dx, dy); 82 | } 83 | textCursorAbsoluteX = tx; 84 | textCursorAbsoluteY = ty; 85 | } 86 | 87 | public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException { 88 | endText(); 89 | pageContentStream.drawImage(image, x, y, width, height); 90 | } 91 | 92 | private Color currentStrokingColor; 93 | 94 | public void setStrokingColor(Color color) throws IOException { 95 | if (color != currentStrokingColor) { 96 | pageContentStream.setStrokingColor(color); 97 | currentStrokingColor = color; 98 | } 99 | } 100 | 101 | private Color currentNonStrokingColor; 102 | 103 | public void setNonStrokingColor(Color color) throws IOException { 104 | if (color != currentNonStrokingColor) { 105 | pageContentStream.setNonStrokingColor(color); 106 | currentNonStrokingColor = color; 107 | } 108 | } 109 | 110 | public void addRect(float x, float y, float width, float height) throws IOException { 111 | endText(); 112 | pageContentStream.addRect(x, y, width, height); 113 | } 114 | 115 | public void moveTo(float x, float y) throws IOException { 116 | endText(); 117 | pageContentStream.moveTo(x, y); 118 | } 119 | 120 | public void lineTo(float x, float y) throws IOException { 121 | endText(); 122 | pageContentStream.lineTo(x, y); 123 | } 124 | 125 | public void stroke() throws IOException { 126 | endText(); 127 | pageContentStream.stroke(); 128 | } 129 | 130 | public void fill() throws IOException { 131 | endText(); 132 | pageContentStream.fill(); 133 | } 134 | 135 | private float currentLineWidth = -1; 136 | 137 | public void setLineWidth(float lineWidth) throws IOException { 138 | if (lineWidth != currentLineWidth) { 139 | endText(); 140 | pageContentStream.setLineWidth(lineWidth); 141 | currentLineWidth = lineWidth; 142 | } 143 | } 144 | 145 | private int currentLineCapStyle = -1; 146 | 147 | public void setLineCapStyle(int lineCapStyle) throws IOException { 148 | if (lineCapStyle != currentLineCapStyle) { 149 | endText(); 150 | pageContentStream.setLineCapStyle(lineCapStyle); 151 | currentLineCapStyle = lineCapStyle; 152 | } 153 | } 154 | 155 | private float[] currentLineDashPattern; 156 | private float currentLineDashPhase; 157 | 158 | public void setLineDashPattern(float[] pattern, float phase) throws IOException { 159 | if ((pattern != currentLineDashPattern && 160 | !Arrays.equals(pattern, currentLineDashPattern)) || phase != currentLineDashPhase) { 161 | endText(); 162 | pageContentStream.setLineDashPattern(pattern, phase); 163 | currentLineDashPattern = pattern; 164 | currentLineDashPhase = phase; 165 | } 166 | } 167 | 168 | public void close() throws IOException { 169 | endText(); 170 | pageContentStream.close(); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/utils/FontUtils.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.apache.pdfbox.pdmodel.PDDocument; 8 | import org.apache.pdfbox.pdmodel.font.PDFont; 9 | import org.apache.pdfbox.pdmodel.font.PDType0Font; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | *

      15 | * Utility methods for fonts 16 | *

      17 | * 18 | * @author hstimac 19 | * @author mkuehne 20 | */ 21 | public final class FontUtils { 22 | 23 | private final static Logger logger = LoggerFactory.getLogger(FontUtils.class); 24 | 25 | private static final class FontMetrics { 26 | private final float ascent; 27 | 28 | private final float descent; 29 | 30 | private final float height; 31 | 32 | public FontMetrics(final float height, final float ascent, final float descent) { 33 | this.height = height; 34 | this.ascent = ascent; 35 | this.descent = descent; 36 | } 37 | } 38 | 39 | /** 40 | *

      41 | * {@link HashMap} for caching {@link FontMetrics} for designated 42 | * {@link PDFont} because {@link FontUtils#getHeight(PDFont, float)} is 43 | * expensive to calculate and the results are only approximate. 44 | */ 45 | private static final Map fontMetrics = new HashMap<>(); 46 | 47 | private static final Map defaultFonts = new HashMap<>(); 48 | 49 | private FontUtils() { 50 | } 51 | 52 | /** 53 | *

      54 | * Loads the {@link PDType0Font} to be embedded in the specified 55 | * {@link PDDocument}. 56 | *

      57 | * 58 | * @param document 59 | * {@link PDDocument} where fonts will be loaded 60 | * @param fontPath 61 | * font path which will be loaded 62 | * @return The read {@link PDType0Font} 63 | */ 64 | public static final PDType0Font loadFont(PDDocument document, String fontPath) { 65 | try { 66 | return PDType0Font.load(document, FontUtils.class.getClassLoader().getResourceAsStream(fontPath)); 67 | } catch (IOException e) { 68 | logger.warn("Cannot load given external font", e); 69 | return null; 70 | } 71 | } 72 | 73 | /** 74 | *

      75 | * Retrieving {@link String} width depending on current font size. The width 76 | * of the string in 1/1000 units of text space. 77 | *

      78 | * 79 | * @param font 80 | * The font of text whose width will be retrieved 81 | * @param text 82 | * The text whose width will be retrieved 83 | * @param fontSize 84 | * The font size of text whose width will be retrieved 85 | * @return text width 86 | */ 87 | public static float getStringWidth(final PDFont font, final String text, final float fontSize) { 88 | try { 89 | return font.getStringWidth(text) / 1000 * fontSize; 90 | } catch (final IOException e) { 91 | // turn into runtime exception 92 | throw new IllegalStateException("Unable to determine text width", e); 93 | } 94 | } 95 | 96 | /** 97 | *

      98 | * Calculate the font ascent distance. 99 | *

      100 | * 101 | * @param font 102 | * The font from which calculation will be applied 103 | * @param fontSize 104 | * The font size from which calculation will be applied 105 | * @return Positive font ascent distance 106 | */ 107 | public static float getAscent(final PDFont font, final float fontSize) { 108 | final String fontName = font.getName(); 109 | if (!fontMetrics.containsKey(fontName)) { 110 | createFontMetrics(font); 111 | } 112 | 113 | return fontMetrics.get(fontName).ascent * fontSize; 114 | } 115 | 116 | /** 117 | *

      118 | * Calculate the font descent distance. 119 | *

      120 | * 121 | * @param font 122 | * The font from which calculation will be applied 123 | * @param fontSize 124 | * The font size from which calculation will be applied 125 | * @return Negative font descent distance 126 | */ 127 | public static float getDescent(final PDFont font, final float fontSize) { 128 | final String fontName = font.getName(); 129 | if (!fontMetrics.containsKey(fontName)) { 130 | createFontMetrics(font); 131 | } 132 | 133 | return fontMetrics.get(fontName).descent * fontSize; 134 | } 135 | 136 | /** 137 | *

      138 | * Calculate the font height. 139 | *

      140 | * 141 | * @param font 142 | * {@link PDFont} from which the height will be calculated. 143 | * @param fontSize 144 | * font size for current {@link PDFont}. 145 | * @return {@link PDFont}'s height 146 | */ 147 | public static float getHeight(final PDFont font, final float fontSize) { 148 | final String fontName = font.getName(); 149 | if (!fontMetrics.containsKey(fontName)) { 150 | createFontMetrics(font); 151 | } 152 | 153 | return fontMetrics.get(fontName).height * fontSize; 154 | } 155 | 156 | /** 157 | *

      158 | * Create basic {@link FontMetrics} for current font. 159 | *

      160 | * 161 | * @param font 162 | * The font from which calculation will be applied <<<<<<< HEAD 163 | * @throws IOException 164 | * If reading the font file fails ======= >>>>>>> using FreeSans 165 | * as default font and added new free fonts 166 | */ 167 | private static void createFontMetrics(final PDFont font) { 168 | final float base = font.getFontDescriptor().getXHeight() / 1000; 169 | final float ascent = font.getFontDescriptor().getAscent() / 1000 - base; 170 | final float descent = font.getFontDescriptor().getDescent() / 1000; 171 | fontMetrics.put(font.getName(), new FontMetrics(base + ascent - descent, ascent, descent)); 172 | } 173 | 174 | public static void addDefaultFonts(final PDFont font, final PDFont fontBold, final PDFont fontItalic, 175 | final PDFont fontBoldItalic) { 176 | defaultFonts.put("font", font); 177 | defaultFonts.put("fontBold", fontBold); 178 | defaultFonts.put("fontItalic", fontItalic); 179 | defaultFonts.put("fontBoldItalic", fontBoldItalic); 180 | } 181 | 182 | public static Map getDefaultfonts() { 183 | return defaultFonts; 184 | } 185 | 186 | public static void setSansFontsAsDefault(PDDocument document) { 187 | defaultFonts.put("font", loadFont(document, "fonts/FreeSans.ttf")); 188 | defaultFonts.put("fontBold", loadFont(document, "fonts/FreeSansBold.ttf")); 189 | defaultFonts.put("fontItalic", loadFont(document, "fonts/FreeSansOblique.ttf")); 190 | defaultFonts.put("fontBoldItalic", loadFont(document, "fonts/FreeSansBoldOblique.ttf")); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | org.sonatype.oss 8 | oss-parent 9 | 9 10 | 11 | 12 | 4.0.0 13 | com.github.dhorions 14 | boxable 15 | 1.7.1-SNAPSHOT 16 | jar 17 | 18 | Boxable, a high-level API to creates table on top of Apache Pdfbox 19 | Easily creates tables in pdf. 20 | 21 | 22 | 23 | Apache-2.0 24 | https://www.apache.org/licenses/LICENSE-2.0 25 | 26 | 27 | 28 | 29 | 30 | org.apache.pdfbox 31 | pdfbox 32 | 3.0.0 33 | compile 34 | 35 | 36 | org.slf4j 37 | slf4j-api 38 | 1.7.36 39 | compile 40 | 41 | 42 | org.apache.commons 43 | commons-csv 44 | 1.9.0 45 | compile 46 | 47 | 48 | 49 | junit 50 | junit 51 | 4.13.2 52 | test 53 | 54 | 55 | commons-io 56 | commons-io 57 | 2.11.0 58 | test 59 | 60 | 61 | org.jsoup 62 | jsoup 63 | 1.15.3 64 | jar 65 | 66 | 67 | 68 | 69 | ossrh 70 | https://oss.sonatype.org/content/repositories/snapshots 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-source-plugin 78 | 3.0.0 79 | 80 | 81 | attach-sources 82 | 83 | jar 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-compiler-plugin 91 | 3.1 92 | 93 | 1.7 94 | 1.7 95 | 96 | ${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar 97 | 98 | 99 | 100 | 101 | external.atlassian.jgitflow 102 | jgitflow-maven-plugin 103 | 1.0-m4.3 104 | 105 | true 106 | true 107 | true 108 | true 109 | true 110 | true 111 | true 112 | true 113 | 114 | 115 | 116 | org.sonatype.plugins 117 | nexus-staging-maven-plugin 118 | 1.6.3 119 | true 120 | 121 | ossrh 122 | https://oss.sonatype.org/ 123 | true 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-gpg-plugin 129 | 1.5 130 | 131 | 132 | sign-artifacts 133 | verify 134 | 135 | sign 136 | 137 | 138 | 139 | 140 | 141 | org.apache.maven.plugins 142 | maven-javadoc-plugin 143 | 3.4.0 144 | 145 | 146 | attach-javadocs 147 | 148 | jar 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | UTF-8 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/main/resources/fonts/USAGE: -------------------------------------------------------------------------------- 1 | Usage of GNU FreeFont 2 | 3 | Language scripts and faces 4 | ========================== 5 | 6 | There are three faces (serif, sans-serif, and monospace), and four styles 7 | (regular, bold, cursive/italic, and bold cursive/italic) for each face. 8 | There is one font file per face/style combination: 12 files in total. 9 | 10 | The letters for various languages, as well as specialized symbols, exist 11 | among the various font files, but they are not uniformly populated. 12 | All the fonts have complete support for Latin, Cyrillic, and Greek, as 13 | well as most of the extensions for those scripts. 14 | 15 | At this time, serif regular has by far the largest number of letters, and 16 | supports the largest number of writing scripts. However there are writing 17 | scripts supported by the sans-serif but not by serif. 18 | 19 | For an overview of which scripts and sets of symbols are supported by 20 | which face, see the FreeFont 'coverage' web page. 21 | 22 | Font features 23 | ============= 24 | 25 | FreeFont has numerous font "features" that perform alterations to the basic 26 | letters of the font, replacing them with other letters, or positioning them 27 | with respect to other letters. 28 | 29 | Many features are activated automatically, but in some environments, they 30 | present some user control. This documents those features with user control. 31 | 32 | Language-specific features 33 | ========================== 34 | 35 | Some OpenType font features are activated only when the text is specified to 36 | be of a certain language. 37 | 38 | This is done in HTML by enclosing the text with a tag whose 'lang' attribute 39 | is set to the appropriate ISO 632.2 language code. In a word processor, 40 | any block of text can be given a language setting. 41 | 42 | Latin 43 | ----- 44 | Catalan ligature improving l·l 45 | Dutch ligatures for ij, IJ 46 | Sami localized form for letter Eng 47 | Turkish overrides ligatures fi ffi of Latin 48 | 49 | Cyrillic 50 | -------- 51 | Ukrainian ligature for double i-diaresis 52 | Serbian/Macedonian localized letters be, and more in italic 53 | Bulgarian style set for modern glyphs 54 | 55 | Hebrew 56 | ------ 57 | Yiddish raised vowels under yo 58 | 59 | Devanagari 60 | ---------- 61 | Sanskrit much larger set of ligatures 62 | 63 | Hindi, Marathi better spacing of Western punctuation marks 64 | 65 | Indic languages 66 | --------------- 67 | 68 | The 'danda' character is encoded in Unicode only in the Devanagari range. 69 | When writing in scripts of other Indic languages, this same character is to 70 | be used. But the shapes and line thicknesses of glyphs vary slightly from 71 | one script to another, so the same glyph for 'danda' may not fit all scripts. 72 | 73 | By specifying the language of the text, an appropriate glyph for 'danda' 74 | will be obtained. 75 | 76 | Style sets 77 | ========== 78 | 79 | These replacements are activated by specifying a "Style Set". 80 | These features are accessible only from typesetting software. 81 | 82 | Cyrillic Bulgarian modern (ss01) 83 | 84 | Devanagari Bombay (ss02), Calcutta (ss03), Nepali (ss04) 85 | 86 | Discretionary features 87 | ====================== 88 | These features are accessible only from typesetting software. 89 | Typically the user must specifically request them. 90 | 91 | Unless otherwise noted, these are available only in FreeSerif. 92 | 93 | Ligatures and substitutions 94 | --------------------------- 95 | 96 | Arabic, Armenian, Hebrew, German, Dutch 97 | 98 | Small captials 99 | -------------- 100 | 101 | A limited set of specially drawn small capital letters in Latin. 102 | 103 | Superscript and subscript 104 | ------------------------- 105 | 106 | Transform a limited set of characters--mostly Latin letters and numerals-- 107 | to versions well-sized and positioned as superscript or subscript. 108 | 109 | Numeral styles 110 | -------------- 111 | 112 | The default numerals of FreeSerif are mono-spaced and of even height. 113 | It also features proportionally-spaced numerals, and "old-style" numerals-- 114 | those which vary in height and sometimes go beneath the baseline. 115 | These can be had at discretion. 116 | 117 | Diagonal fractions 118 | ------------------ 119 | 120 | A limited set of diagonal fraction substitutions are available at discretion. 121 | The set is more than what is encoded in Unicode. 122 | They work with the ASCII slash or the mathematical slash U+2215. 123 | The transform a sequence "number-slash-number" to a diagonal form. 124 | 125 | Zero 126 | ---- 127 | 128 | A slashed form of the numeral zero is available at discretion. 129 | Available in all faces. 130 | 131 | Alternative characters 132 | ====================== 133 | 134 | FreeSerif has some listings of alternatives for specific characters. 135 | Again this is use primarily in specialized typesetting software. 136 | 137 | Greek, Latin 138 | 139 | Use in LaTeX 140 | ============ 141 | It is possible to use Unicode fonts in recent LaTeX implementations, but in 142 | LuaTeX http://www.luatex.org/ and 143 | XeTeX http://tug.org/xetex/ 144 | it is particularly easy to use Unicode text, and to enable font features. 145 | Recent versions of these systems use the 'fontspec' package to choose fonts 146 | and features. 147 | 148 | A very simple document might contain the lines 149 | --------------------------------------------------------------------------- 150 | \documentclass{ltxdockit} 151 | \usepackage{fontspec} 152 | \usepackage{xunicode} 153 | \setmainfont[]{FreeSerif} 154 | \begin{document} 155 | {\fontspec[Script=Default,Fractions={On}]{FreeSerif} 156 | 1/7 3/10 7/10} 157 | 158 | x\raisebox{-0.5ex}{{\scriptsize ai}} 159 | x{\fontspec[Script=Default,VerticalPosition={Inferior}]{FreeSerif} 160 | abcdefghijklmnopqrstuvwxyz+−(0123456789)} \\ 161 | x\raisebox{0.85ex}{{\scriptsize ai}} 162 | x{\fontspec[Script=Default,VerticalPosition={Superior}]{FreeSerif} 163 | abcdefghijklmnopqrstuvwxyz+−(0123456789)} 164 | 165 | {\fontspec[Script=Latin]{FreeSerif} 166 | \textsc{Small Caps} } 167 | 168 | { Bсички хора се раждат свободни и равни по достойнство и права. 169 | \fontspec[Script=Cyrillic,Language=Bulgarian,Variant={1}]{FreeSerif} \selectfont 170 | Bсички хора се раждат свободни и равни по достойнство и права. } 171 | 172 | \end{document} 173 | --------------------------------------------------------------------------- 174 | Here are some 'fontspec' setting-value pairs meaningful for FreeFont. 175 | 176 | Numbers: Lining OldStyle Proportional SlashedZero 177 | Fractions: On 178 | VerticalPosition: Superior Inferior 179 | Ligatures: Common Historical 180 | Letters: UppercaseSmallCaps 181 | Variant: 1 (etc. -- must be in {} picks style set.) 182 | --------------------------------------------------------------------------- 183 | 184 | 185 | $Id: usage.txt,v 1.10 2011-07-16 08:38:06 Stevan_White Exp $ 186 | -------------------------------------------------------------------------------- /src/main/resources/fonts/AUTHORS: -------------------------------------------------------------------------------- 1 | -*- mode:text; coding:utf-8; -*- 2 | GNU FreeFont Authors 3 | ==================== 4 | 5 | The FreeFont collection is being maintained by 6 | Steve White 7 | The folowing list cites the other contributors that contributed to 8 | particular ISO 10646 blocks. 9 | 10 | * URW++ Design & Development GmbH 11 | 12 | Basic Latin (U+0041-U+007A) 13 | Latin-1 Supplement (U+00C0-U+00FF) (most) 14 | Latin Extended-A (U+0100-U+017F) 15 | Spacing Modifier Letters (U+02B0-U+02FF) 16 | Mathematical Operators (U+2200-U+22FF) (parts) 17 | Block Elements (U+2580-U+259F) 18 | Dingbats (U+2700-U+27BF) 19 | 20 | * Yannis Haralambous and John 21 | Plaice 22 | 23 | Latin Extended-B (U+0180-U+024F) 24 | IPA Extensions (U+0250-U+02AF) 25 | Greek (U+0370-U+03FF) 26 | Armenian (U+0530-U+058F) 27 | Hebrew (U+0590-U+05FF) 28 | Arabic (U+0600-U+06FF) 29 | Currency Symbols (U+20A0-U+20CF) 30 | Arabic Presentation Forms-A (U+FB50-U+FDFF) 31 | Arabic Presentation Forms-B (U+FE70-U+FEFF) 32 | 33 | * Yannis Haralambous and Wellcome Institute 34 | 35 | Sinhala (U+0D80-U+0DFF) 36 | 37 | * Young U. Ryu 38 | 39 | Arrows (U+2190-U+21FF) 40 | Mathematical Symbols (U+2200-U+22FF) 41 | Mathematical Alphanumeric Symbols (U+1D400-U+1D7FF) 42 | 43 | * Valek Filippov 44 | 45 | Cyrillic (U+0400-U+04FF) 46 | 47 | * Wadalab Kanji Comittee 48 | 49 | Hiragana (U+3040-U+309F) 50 | Katakana (U+30A0-U+30FF) 51 | 52 | * Angelo Haritsis 53 | 54 | Greek (U+0370-U+03FF) 55 | 56 | * Yannis Haralambous and Virach Sornlertlamvanich 57 | 58 | Thai (U+0E00-U+0E7F) 59 | 60 | * Shaheed R. Haque 61 | 62 | Bengali (U+0980-U+09FF) 63 | 64 | * Sam Stepanyan 65 | 66 | Armenian (U+0530-U+058F) 67 | 68 | * Mohamed Ishan 69 | 70 | Thaana (U+0780-U+07BF) 71 | 72 | * Sushant Kumar Dash 73 | 74 | Oriya (U+0B00-U+0B7F) 75 | 76 | * Harsh Kumar 77 | 78 | Devanagari (U+0900-U+097F) 79 | Bengali (U+0980-U+09FF) 80 | Gurmukhi (U+0A00-U+0A7F) 81 | Gujarati (U+0A80-U+0AFF) 82 | 83 | * Prasad A. Chodavarapu 84 | 85 | Telugu (U+0C00-U+0C7F) 86 | 87 | * Frans Velthuis and Anshuman Pandey 88 | 89 | 90 | Devanagari (U+0900-U+097F) 91 | 92 | * Hardip Singh Pannu 93 | 94 | Gurmukhi (U+0A00-U+0A7F) 95 | 96 | * Jeroen Hellingman 97 | 98 | Oriya (U+0B00-U+0B7F) 99 | Malayalam (U+0D00-U+0D7F) 100 | 101 | * Thomas Ridgeway 102 | 103 | Tamil (U+0B80-U+0BFF) 104 | 105 | * Berhanu Beyene <1beyene AT informatik.uni-hamburg.de>, 106 | Prof. Dr. Manfred Kudlek , Olaf 107 | Kummer , and Jochen Metzinger 108 | 109 | Ethiopic (U+1200-U+137F) 110 | 111 | * Maxim Iorsh 112 | 113 | Hebrew (U+0590-U+05FF) 114 | 115 | * Vyacheslav Dikonov 116 | 117 | Syriac (U+0700-U+074A) 118 | Braille (U+2800-U+28FF) 119 | 120 | * Panayotis Katsaloulis 121 | 122 | Greek Extended (U+1F00-U+1FFF) 123 | 124 | * M.S. Sridhar 125 | 126 | Devanagari (U+0900-U+097F) 127 | Bengali (U+0980-U+09FF) 128 | Gurmukhi (U+0A00-U+0A7F) 129 | Gujarati (U+0A80-U+0AFF) 130 | Oriya (U+0B00-U+0B7F) 131 | Tamil (U+0B80-U+0BFF) 132 | Telugu (U+0C00-U+0C7F) 133 | Kannada (U+0C80-U+0CFF) 134 | Malayalam (U+0D00-U+0D7F) 135 | 136 | * DMS Electronics, The Sri Lanka Tipitaka Project, and Noah Levitt 137 | 138 | 139 | Sinhala (U+0D80-U+0DFF) 140 | 141 | * Dan Shurovich Chirkov 142 | 143 | Cyrillic (U+0400-U+04FF) 144 | 145 | * Abbas Izad 146 | 147 | Arabic (U+0600-U+06FF) 148 | Arabic Presentation Forms-A (U+FB50-U+FDFF) 149 | Arabic Presentation Forms-B (U+FE70-U+FEFF) 150 | 151 | * Denis Jacquerye 152 | 153 | Latin Extended-B (U+0180-U+024F) 154 | IPA Extensions (U+0250-U+02AF) 155 | 156 | * K.H. Hussain and R. Chitrajan 157 | 158 | Malayalam (U+0D00-U+0D7F) 159 | 160 | * Solaiman Karim and Omi Azad 161 | 162 | Bengali (U+0980-U+09FF) 163 | 164 | * Sonali Sonania and Monika Shah 165 | 166 | 167 | Devanagari (U+0900-U+097F) 168 | Gujarati (U+0A80-U+0AFF) 169 | 170 | * Pravin Satpute , Bageshri Salvi 171 | , Rahul Bhalerao and 172 | Sandeep Shedmake 173 | 174 | Devanagari (U+0900-U+097F) 175 | Gujarati (U+0A80-U+0AFF) 176 | Oriya (U+0B00-U+0B7F) 177 | Malayalam (U+0D00-U+0D7F) 178 | Tamil (U+0B80-U+0BFF) 179 | 180 | * Kulbir Singh Thind 181 | 182 | Gurmukhi (U+0A00-U+0A7F) 183 | 184 | * Gia Shervashidze 185 | 186 | Georgian (U+10A0-U+10FF) 187 | 188 | * Daniel Johnson 189 | 190 | Armenian (serif) (U+0530-U+058F) 191 | Cherokee (U+13A0-U+13FF) 192 | Unified Canadian Aboriginal Syllabics (U+1400-U+167F) 193 | UCAS Extended (U+18B0-U+18F5) 194 | Tifinagh (U+2D30-U+2D7F) 195 | Vai (U+A500-U+A62B) 196 | Latin Extended-D (Mayanist letters) (U+A720-U+A7FF) 197 | Kayah Li (U+A900-U+A92F) 198 | Osmanya (U+10480-U+104a7) 199 | 200 | * George Douros 201 | 202 | Gothic (U+10330-U+1034F) 203 | Phoenecian (U+10900-U+1091F) 204 | Byzantine Musical Symbols (U+1D000-U+1D0FF) 205 | Western Musical Symbols (U+1D100-U+1D1DF) 206 | Mathematical Alphanumeric Symbols (U+1D400-U+1D7FF) 207 | Mah Jong Tiles (U+1F000-U+1F02B) 208 | Dominoes (U+1F030-U+1F093) 209 | 210 | * Steve White 211 | Glagolitic (U+2C00-U+2C5F) 212 | Coptic (U+2C80-U+2CFF) 213 | Arabic (U+0600-U+06FF) (Mono) 214 | Old Italic (U+10300-U+1032F) 215 | 216 | * Pavel Skrylev is responsible for 217 | Cyrillic Extended-A (U+2DEO-U+2DFF) 218 | as well as many of the additions to 219 | Cyrillic Extended-B (U+A640-U+A65F) 220 | 221 | * Mark Williamson 222 | Made the MPH 2 Damase font, from which 223 | Hanunóo (U+1720-U+173F) 224 | Buginese (U+1A00-U+1A1F) 225 | Tai Le (U+1950-U+197F) 226 | Ugaritic (U+10380-U+1039F) 227 | Old Persian (U+103A0-U+103DF) 228 | 229 | * Masoud Pourmoosa 230 | Arabic (U+0600-U+06FF) 231 | 232 | * Emmanuel Vallois 233 | Python scripts, support 234 | 235 | * Primož Peterlin 236 | maintained FreeFont for several years, and is thanked for all his work. 237 | 238 | Please see the CREDITS file for details on who contributed particular 239 | subsets of the glyphs in font files. 240 | 241 | -------------------------------------------------------------------------- 242 | $Id: AUTHORS,v 1.23 2010-09-11 13:24:11 Stevan_White Exp $ 243 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/Row.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Quodlibet.be 4 | */ 5 | package be.quodlibet.boxable; 6 | 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.apache.pdfbox.pdmodel.PDDocument; 12 | import org.apache.pdfbox.pdmodel.PDPage; 13 | import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem; 14 | 15 | import be.quodlibet.boxable.image.Image; 16 | 17 | public class Row { 18 | 19 | private final Table table; 20 | PDOutlineItem bookmark; 21 | List> cells; 22 | private boolean headerRow = false; 23 | float height; 24 | private float lineSpacing = 1; 25 | 26 | Row(Table table, List> cells, float height) { 27 | this.table = table; 28 | this.cells = cells; 29 | this.height = height; 30 | } 31 | 32 | Row(Table table, float height) { 33 | this.table = table; 34 | this.cells = new ArrayList<>(); 35 | this.height = height; 36 | } 37 | 38 | /** 39 | *

      40 | * Creates a cell with provided width, cell value and default left top 41 | * alignment 42 | *

      43 | * 44 | * @param width 45 | * Absolute width in points or in % of table width 46 | * @param value 47 | * Cell's value (content) 48 | * @return New {@link Cell} 49 | */ 50 | public Cell createCell(float width, String value) { 51 | Cell cell = new Cell(this, width, value, true); 52 | if (headerRow) { 53 | // set all cell as header cell 54 | cell.setHeaderCell(true); 55 | } 56 | setBorders(cell, cells.isEmpty()); 57 | cell.setLineSpacing(lineSpacing); 58 | cells.add(cell); 59 | return cell; 60 | } 61 | 62 | /** 63 | *

      64 | * Creates an image cell with provided width and {@link Image} 65 | *

      66 | * 67 | * @param width 68 | * Cell's width 69 | * @param img 70 | * {@link Image} in the cell 71 | * @return {@link ImageCell} 72 | */ 73 | public ImageCell createImageCell(float width, Image img) { 74 | ImageCell cell = new ImageCell<>(this, width, img, true); 75 | setBorders(cell, cells.isEmpty()); 76 | cells.add(cell); 77 | return cell; 78 | } 79 | 80 | public Cell createImageCell(float width, Image img, HorizontalAlignment align, VerticalAlignment valign) { 81 | Cell cell = new ImageCell(this, width, img, true, align, valign); 82 | setBorders(cell, cells.isEmpty()); 83 | cells.add(cell); 84 | return cell; 85 | } 86 | 87 | /** 88 | *

      89 | * Creates a table cell with provided width and table data 90 | *

      91 | * 92 | * @param width 93 | * Table width 94 | * @param tableData 95 | * Table's data (HTML table tags) 96 | * @param doc 97 | * {@link PDDocument} where this table will be drawn 98 | * @param page 99 | * {@link PDPage} where this table cell will be drawn 100 | * @param yStart 101 | * Y position from which table will be drawn 102 | * @param pageTopMargin 103 | * {@link TableCell}'s top margin 104 | * @param pageBottomMargin 105 | * {@link TableCell}'s bottom margin 106 | * @return {@link TableCell} with provided width and table data 107 | */ 108 | public TableCell createTableCell(float width, String tableData, PDDocument doc, PDPage page, float yStart, 109 | float pageTopMargin, float pageBottomMargin) { 110 | TableCell cell = new TableCell(this, width, tableData, true, doc, page, yStart, pageTopMargin, 111 | pageBottomMargin); 112 | setBorders(cell, cells.isEmpty()); 113 | cells.add(cell); 114 | return cell; 115 | } 116 | 117 | /** 118 | *

      119 | * Creates a cell with provided width, cell value, horizontal and vertical 120 | * alignment 121 | *

      122 | * 123 | * @param width 124 | * Absolute width in points or in % of table width 125 | * @param value 126 | * Cell's value (content) 127 | * @param align 128 | * Cell's {@link HorizontalAlignment} 129 | * @param valign 130 | * Cell's {@link VerticalAlignment} 131 | * @return New {@link Cell} 132 | */ 133 | public Cell createCell(float width, String value, HorizontalAlignment align, VerticalAlignment valign) { 134 | Cell cell = new Cell(this, width, value, true, align, valign); 135 | if (headerRow) { 136 | // set all cell as header cell 137 | cell.setHeaderCell(true); 138 | } 139 | setBorders(cell, cells.isEmpty()); 140 | cell.setLineSpacing(lineSpacing); 141 | cells.add(cell); 142 | return cell; 143 | } 144 | 145 | /** 146 | *

      147 | * Creates a cell with the same width as the corresponding header cell 148 | *

      149 | * 150 | * @param value 151 | * Cell's value (content) 152 | * @return new {@link Cell} 153 | */ 154 | public Cell createCell(String value) { 155 | float headerCellWidth = table.getHeader().getCells().get(cells.size()).getWidth(); 156 | Cell cell = new Cell(this, headerCellWidth, value, false); 157 | setBorders(cell, cells.isEmpty()); 158 | cells.add(cell); 159 | return cell; 160 | } 161 | 162 | /** 163 | *

      164 | * Remove left border to avoid double borders from previous cell's right 165 | * border. In most cases left border will be removed. 166 | *

      167 | * 168 | * @param cell 169 | * {@link Cell} 170 | * @param leftBorder 171 | * boolean for drawing cell's left border. If {@code true} then 172 | * the left cell's border will be drawn. 173 | */ 174 | private void setBorders(final Cell cell, final boolean leftBorder) { 175 | if (!leftBorder) { 176 | cell.setLeftBorderStyle(null); 177 | } 178 | } 179 | 180 | /** 181 | *

      182 | * remove top borders of cells to avoid double borders from cells in 183 | * previous row 184 | *

      185 | */ 186 | void removeTopBorders() { 187 | for (final Cell cell : cells) { 188 | cell.setTopBorderStyle(null); 189 | } 190 | } 191 | 192 | /** 193 | *

      194 | * Remove all borders of cells. 195 | *

      196 | */ 197 | void removeAllBorders() { 198 | for (final Cell cell : cells) { 199 | cell.setBorderStyle(null); 200 | ; 201 | } 202 | } 203 | 204 | /** 205 | *

      206 | * Gets maximal height of the cells in current row therefore row's height. 207 | *

      208 | * 209 | * @return Row's height 210 | */ 211 | public float getHeight() { 212 | float maxheight = 0.0f; 213 | for (Cell cell : this.cells) { 214 | float cellHeight = cell.getCellHeight(); 215 | 216 | if (cellHeight > maxheight) { 217 | maxheight = cellHeight; 218 | } 219 | } 220 | 221 | if (maxheight > height) { 222 | this.height = maxheight; 223 | } 224 | return height; 225 | } 226 | 227 | public float getLineHeight() throws IOException { 228 | return height; 229 | } 230 | 231 | public void setHeight(float height) { 232 | this.height = height; 233 | } 234 | 235 | public List> getCells() { 236 | return cells; 237 | } 238 | 239 | public int getColCount() { 240 | return cells.size(); 241 | } 242 | 243 | public void setCells(List> cells) { 244 | this.cells = cells; 245 | } 246 | 247 | public float getWidth() { 248 | return table.getWidth(); 249 | } 250 | 251 | public PDOutlineItem getBookmark() { 252 | return bookmark; 253 | } 254 | 255 | public void setBookmark(PDOutlineItem bookmark) { 256 | this.bookmark = bookmark; 257 | } 258 | 259 | protected float getLastCellExtraWidth() { 260 | float cellWidth = 0; 261 | for (Cell cell : cells) { 262 | cellWidth += cell.getWidth(); 263 | } 264 | 265 | float lastCellExtraWidth = this.getWidth() - cellWidth; 266 | return lastCellExtraWidth; 267 | } 268 | 269 | public float xEnd() { 270 | return table.getMargin() + getWidth(); 271 | } 272 | 273 | public boolean isHeaderRow() { 274 | return headerRow; 275 | } 276 | 277 | public void setHeaderRow(boolean headerRow) { 278 | this.headerRow = headerRow; 279 | } 280 | 281 | public float getLineSpacing() { 282 | return lineSpacing; 283 | } 284 | 285 | public void setLineSpacing(float lineSpacing) { 286 | this.lineSpacing = lineSpacing; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/text/Tokenizer.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable.text; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Stack; 7 | 8 | public final class Tokenizer { 9 | 10 | private static final Token OPEN_TAG_I = new Token(TokenType.OPEN_TAG, "i"); 11 | private static final Token OPEN_TAG_B = new Token(TokenType.OPEN_TAG, "b"); 12 | private static final Token OPEN_TAG_OL = new Token(TokenType.OPEN_TAG, "ol"); 13 | private static final Token OPEN_TAG_UL = new Token(TokenType.OPEN_TAG, "ul"); 14 | private static final Token CLOSE_TAG_I = new Token(TokenType.CLOSE_TAG, "i"); 15 | private static final Token CLOSE_TAG_B = new Token(TokenType.CLOSE_TAG, "b"); 16 | private static final Token CLOSE_TAG_OL = new Token(TokenType.CLOSE_TAG, "ol"); 17 | private static final Token CLOSE_TAG_UL = new Token(TokenType.CLOSE_TAG, "ul"); 18 | private static final Token CLOSE_TAG_P = new Token(TokenType.CLOSE_TAG, "p"); 19 | private static final Token CLOSE_TAG_LI = new Token(TokenType.CLOSE_TAG, "li"); 20 | private static final Token POSSIBLE_WRAP_POINT = new Token(TokenType.POSSIBLE_WRAP_POINT, ""); 21 | private static final Token WRAP_POINT_P = new Token(TokenType.WRAP_POINT, "p"); 22 | private static final Token WRAP_POINT_LI = new Token(TokenType.WRAP_POINT, "li"); 23 | private static final Token WRAP_POINT_BR = new Token(TokenType.WRAP_POINT, "br"); 24 | 25 | private Tokenizer() { 26 | } 27 | 28 | private static boolean isWrapPointChar(char ch) { 29 | return 30 | ch == ' ' || 31 | ch == ',' || 32 | ch == '.' || 33 | ch == '-' || 34 | ch == '@' || 35 | ch == ':' || 36 | ch == ';' || 37 | ch == '\n' || 38 | ch == '\t' || 39 | ch == '\r' || 40 | ch == '\f' || 41 | ch == '\u000B'; 42 | } 43 | 44 | private static Stack findWrapPoints(String text) { 45 | Stack result = new Stack<>(); 46 | result.push(text.length()); 47 | for (int i = text.length() - 2; i >= 0; i--) { 48 | if (isWrapPointChar(text.charAt(i))) { 49 | result.push(i + 1); 50 | } 51 | } 52 | return result; 53 | } 54 | 55 | private static Stack findWrapPointsWithFunction(String text, WrappingFunction wrappingFunction) { 56 | final String[] split = wrappingFunction.getLines(text); 57 | int textIndex = text.length(); 58 | final Stack possibleWrapPoints = new Stack<>(); 59 | possibleWrapPoints.push(textIndex); 60 | for (int i = split.length - 1; i > 0; i--) { 61 | final int splitLength = split[i].length(); 62 | possibleWrapPoints.push(textIndex - splitLength); 63 | textIndex -= splitLength; 64 | } 65 | return possibleWrapPoints; 66 | } 67 | 68 | public static List tokenize(final String text, final WrappingFunction wrappingFunction) { 69 | final List tokens = new ArrayList<>(); 70 | if (text != null) { 71 | final Stack possibleWrapPoints = wrappingFunction == null 72 | ? findWrapPoints(text) 73 | : findWrapPointsWithFunction(text, wrappingFunction); 74 | int textIndex = 0; 75 | final StringBuilder sb = new StringBuilder(); 76 | // taking first wrap point 77 | Integer currentWrapPoint = possibleWrapPoints.pop(); 78 | while (textIndex < text.length()) { 79 | if (textIndex == currentWrapPoint) { 80 | if (sb.length() > 0) { 81 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 82 | sb.delete(0, sb.length()); 83 | } 84 | tokens.add(POSSIBLE_WRAP_POINT); 85 | currentWrapPoint = possibleWrapPoints.pop(); 86 | } 87 | final char c = text.charAt(textIndex); 88 | switch (c) { 89 | case '<': 90 | boolean consumed = false; 91 | if (textIndex < text.length() - 2) { 92 | final char lookahead1 = text.charAt(textIndex + 1); 93 | final char lookahead2 = text.charAt(textIndex + 2); 94 | if ('i' == lookahead1 && '>' == lookahead2) { 95 | // 96 | if (sb.length() > 0) { 97 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 98 | // clean string builder 99 | sb.delete(0, sb.length()); 100 | } 101 | tokens.add(OPEN_TAG_I); 102 | textIndex += 2; 103 | consumed = true; 104 | } else if ('b' == lookahead1 && '>' == lookahead2) { 105 | // 106 | if (sb.length() > 0) { 107 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 108 | // clean string builder 109 | sb.delete(0, sb.length()); 110 | } 111 | tokens.add(OPEN_TAG_B); 112 | textIndex += 2; 113 | consumed = true; 114 | } else if ('b' == lookahead1 && 'r' == lookahead2) { 115 | if (textIndex < text.length() - 3) { 116 | //
      117 | final char lookahead3 = text.charAt(textIndex + 3); 118 | if (lookahead3 == '>') { 119 | if (sb.length() > 0) { 120 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 121 | // clean string builder 122 | sb.delete(0, sb.length()); 123 | } 124 | tokens.add(WRAP_POINT_BR); 125 | // normal notation
      126 | textIndex += 3; 127 | consumed = true; 128 | } else if (textIndex < text.length() - 4) { 129 | //
      130 | final char lookahead4 = text.charAt(textIndex + 4); 131 | if (lookahead3 == '/' && lookahead4 == '>') { 132 | if (sb.length() > 0) { 133 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 134 | // clean string builder 135 | sb.delete(0, sb.length()); 136 | } 137 | tokens.add(WRAP_POINT_BR); 138 | // normal notation
      139 | textIndex += 4; 140 | consumed = true; 141 | } else if (textIndex < text.length() - 5) { 142 | final char lookahead5 = text.charAt(textIndex + 5); 143 | if (lookahead3 == ' ' && lookahead4 == '/' && lookahead5 == '>') { 144 | if (sb.length() > 0) { 145 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 146 | // clean string builder 147 | sb.delete(0, sb.length()); 148 | } 149 | tokens.add(WRAP_POINT_BR); 150 | // in case it is notation
      151 | textIndex += 5; 152 | consumed = true; 153 | } 154 | } 155 | } 156 | } 157 | } else if ('p' == lookahead1 && '>' == lookahead2) { 158 | //

      159 | if (sb.length() > 0) { 160 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 161 | // clean string builder 162 | sb.delete(0, sb.length()); 163 | } 164 | tokens.add(WRAP_POINT_P); 165 | textIndex += 2; 166 | consumed = true; 167 | } else if ('o' == lookahead1 && 'l' == lookahead2) { 168 | //

        169 | if (textIndex < text.length() - 3) { 170 | final char lookahead3 = text.charAt(textIndex + 3); 171 | if (lookahead3 == '>') { 172 | if (sb.length() > 0) { 173 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 174 | // clean string builder 175 | sb.delete(0, sb.length()); 176 | } 177 | tokens.add(OPEN_TAG_OL); 178 | textIndex += 3; 179 | consumed = true; 180 | } 181 | } 182 | } else if ('u' == lookahead1 && 'l' == lookahead2) { 183 | //
          184 | if (textIndex < text.length() - 3) { 185 | final char lookahead3 = text.charAt(textIndex + 3); 186 | if (lookahead3 == '>') { 187 | if (sb.length() > 0) { 188 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 189 | // clean string builder 190 | sb.delete(0, sb.length()); 191 | } 192 | tokens.add(OPEN_TAG_UL); 193 | textIndex += 3; 194 | consumed = true; 195 | } 196 | } 197 | } else if ('l' == lookahead1 && 'i' == lookahead2) { 198 | //
        • 199 | if (textIndex < text.length() - 3) { 200 | final char lookahead3 = text.charAt(textIndex + 3); 201 | if (lookahead3 == '>') { 202 | if (sb.length() > 0) { 203 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 204 | // clean string builder 205 | sb.delete(0, sb.length()); 206 | } 207 | tokens.add(WRAP_POINT_LI); 208 | textIndex += 3; 209 | consumed = true; 210 | } 211 | } 212 | } else if ('/' == lookahead1) { 213 | // one character tags 214 | if (textIndex < text.length() - 3) { 215 | final char lookahead3 = text.charAt(textIndex + 3); 216 | if ('>' == lookahead3) { 217 | if ('i' == lookahead2) { 218 | // 219 | if (sb.length() > 0) { 220 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 221 | sb.delete(0, sb.length()); 222 | } 223 | tokens.add(CLOSE_TAG_I); 224 | textIndex += 3; 225 | consumed = true; 226 | } else if ('b' == lookahead2) { 227 | // 228 | if (sb.length() > 0) { 229 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 230 | sb.delete(0, sb.length()); 231 | } 232 | tokens.add(CLOSE_TAG_B); 233 | textIndex += 3; 234 | consumed = true; 235 | } else if ('p' == lookahead2) { 236 | //

          237 | if (sb.length() > 0) { 238 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 239 | sb.delete(0, sb.length()); 240 | } 241 | tokens.add(CLOSE_TAG_P); 242 | textIndex += 3; 243 | consumed = true; 244 | } 245 | } 246 | } 247 | if (textIndex < text.length() - 4) { 248 | // lists 249 | final char lookahead3 = text.charAt(textIndex + 3); 250 | final char lookahead4 = text.charAt(textIndex + 4); 251 | if ('l' == lookahead3) { 252 | if ('o' == lookahead2 && '>' == lookahead4) { 253 | //
      254 | if (sb.length() > 0) { 255 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 256 | sb.delete(0, sb.length()); 257 | } 258 | tokens.add(CLOSE_TAG_OL); 259 | textIndex += 4; 260 | consumed = true; 261 | } else if ('u' == lookahead2 && '>' == lookahead4) { 262 | //
263 | if (sb.length() > 0) { 264 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 265 | sb.delete(0, sb.length()); 266 | } 267 | tokens.add(CLOSE_TAG_UL); 268 | textIndex += 4; 269 | consumed = true; 270 | } 271 | } else if ('l' == lookahead2 && 'i' == lookahead3) { 272 | // 273 | if ('>' == lookahead4) { 274 | if (sb.length() > 0) { 275 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 276 | sb.delete(0, sb.length()); 277 | } 278 | tokens.add(CLOSE_TAG_LI); 279 | textIndex += 4; 280 | consumed = true; 281 | } 282 | } 283 | } 284 | } 285 | 286 | } 287 | if (!consumed) { 288 | sb.append('<'); 289 | } 290 | break; 291 | default: 292 | sb.append(c); 293 | break; 294 | } 295 | textIndex++; 296 | } 297 | 298 | if (sb.length() > 0) { 299 | tokens.add(Token.text(TokenType.TEXT, sb.toString())); 300 | sb.delete(0, sb.length()); 301 | } 302 | tokens.add(POSSIBLE_WRAP_POINT); 303 | 304 | return tokens; 305 | } else 306 | 307 | { 308 | return Collections.emptyList(); 309 | } 310 | } 311 | 312 | } 313 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/TableCell.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import be.quodlibet.boxable.utils.PageContentStreamOptimized; 8 | import org.apache.pdfbox.pdmodel.PDDocument; 9 | import org.apache.pdfbox.pdmodel.PDPage; 10 | import org.apache.pdfbox.pdmodel.PDPageContentStream; 11 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 12 | import org.apache.pdfbox.pdmodel.font.PDFont; 13 | import org.jsoup.Jsoup; 14 | import org.jsoup.nodes.Document; 15 | import org.jsoup.nodes.Element; 16 | import org.jsoup.select.Elements; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import be.quodlibet.boxable.text.Token; 21 | import be.quodlibet.boxable.utils.FontUtils; 22 | import be.quodlibet.boxable.utils.PDStreamUtils; 23 | 24 | public class TableCell extends Cell { 25 | 26 | private final static Logger logger = LoggerFactory.getLogger(TableCell.class); 27 | 28 | private final String tableData; 29 | private final float width; 30 | private float yStart; 31 | private float xStart; 32 | private float height = 0; 33 | private final PDDocument doc; 34 | private final PDPage page; 35 | private float marginBetweenElementsY = FontUtils.getHeight(getFont(), getFontSize()); 36 | private final HorizontalAlignment align; 37 | private final VerticalAlignment valign; 38 | 39 | // default FreeSans font 40 | // private PDFont font = FontUtils.getDefaultfonts().get("font"); 41 | // private PDFont fontBold = FontUtils.getDefaultfonts().get("fontBold"); 42 | private PageContentStreamOptimized tableCellContentStream; 43 | 44 | // page margins 45 | private final float pageTopMargin; 46 | private final float pageBottomMargin; 47 | // default title fonts 48 | private int tableTitleFontSize = 8; 49 | 50 | TableCell(Row row, float width, String tableData, boolean isCalculated, PDDocument document, PDPage page, 51 | float yStart, float pageTopMargin, float pageBottomMargin) { 52 | this(row, width, tableData, isCalculated, document, page, yStart, pageTopMargin, pageBottomMargin, 53 | HorizontalAlignment.LEFT, VerticalAlignment.TOP); 54 | } 55 | 56 | TableCell(Row row, float width, String tableData, boolean isCalculated, PDDocument document, PDPage page, 57 | float yStart, float pageTopMargin, float pageBottomMargin, final HorizontalAlignment align, 58 | final VerticalAlignment valign) { 59 | super(row, width, tableData, isCalculated); 60 | this.tableData = tableData; 61 | this.width = width * row.getWidth() / 100; 62 | this.doc = document; 63 | this.page = page; 64 | this.yStart = yStart; 65 | this.pageTopMargin = pageTopMargin; 66 | this.pageBottomMargin = pageBottomMargin; 67 | this.align = align; 68 | this.valign = valign; 69 | fillTable(); 70 | } 71 | 72 | /** 73 | *

74 | * This method just fills up the table's with her content for proper table 75 | * cell height calculation. Position of the table (x,y) is not relevant 76 | * here. 77 | *

78 | *

79 | * NOTE: if entire row is not header row then use bold instead header cell ( 80 | * {@code 81 | * 82 | }) 83 | *

84 | */ 85 | @SuppressWarnings({ "unused", "unchecked" }) 86 | public void fillTable() { 87 | try { 88 | // please consider the cell's paddings 89 | float tableWidth = this.width - getLeftPadding() - getRightPadding(); 90 | tableCellContentStream = new PageContentStreamOptimized(new PDPageContentStream(doc, page, 91 | PDPageContentStream.AppendMode.APPEND, true)); 92 | // check if there is some additional text outside inner table 93 | String[] outerTableText = tableData.split(""); 105 | for (String chunkie : chunks) { 106 | if (chunkie.contains(" row = table.createRow(0); 153 | Elements tableCols = htmlTableRow.select("td"); 154 | Elements tableHeaderCols = htmlTableRow.select("th"); 155 | // do we have header columns? 156 | boolean tableHasHeaderColumns = tableHeaderCols.isEmpty() ? false : true; 157 | if (tableHasHeaderColumns) { 158 | // if entire row is not header row then use bold instead 159 | // header cell () 160 | row.setHeaderRow(true); 161 | } 162 | int columnsSize = tableHasHeaderColumns ? tableHeaderCols.size() : tableCols.size(); 163 | // calculate how much really columns do you have (including 164 | // colspans!) 165 | for (Element col : tableHasHeaderColumns ? tableHeaderCols : tableCols) { 166 | if (col.attr("colspan") != null && !col.attr("colspan").isEmpty()) { 167 | columnsSize += Integer.parseInt(col.attr("colspan")) - 1; 168 | } 169 | } 170 | for (Element col : tableHasHeaderColumns ? tableHeaderCols : tableCols) { 171 | if (col.attr("colspan") != null && !col.attr("colspan").isEmpty()) { 172 | Cell cell = (Cell) row.createCell( 173 | tableWidth / columnsSize * Integer.parseInt(col.attr("colspan")) / row.getWidth() * 100, 174 | col.html().replace("&", "&")); 175 | } else { 176 | Cell cell = (Cell) row.createCell(tableWidth / columnsSize / row.getWidth() * 100, 177 | col.html().replace("&", "&")); 178 | } 179 | } 180 | yStart -= row.getHeight(); 181 | } 182 | if (drawTable) { 183 | table.draw(); 184 | } 185 | 186 | height += table.getHeaderAndDataHeight() + marginBetweenElementsY; 187 | } 188 | 189 | /** 190 | *

191 | * Method provides writing or height calculation of possible outer text 192 | *

193 | * 194 | * @param paragraph 195 | * Paragraph that needs to be written or whose height needs to be 196 | * calculated 197 | * @param onlyCalculateHeight 198 | * if true the given paragraph will not be drawn 199 | * just his height will be calculated. 200 | * @return Y position after calculating/writing given paragraph 201 | */ 202 | private float writeOrCalculateParagraph(Paragraph paragraph, boolean onlyCalculateHeight) throws IOException { 203 | int boldCounter = 0; 204 | int italicCounter = 0; 205 | 206 | if (!onlyCalculateHeight) { 207 | tableCellContentStream.setRotated(isTextRotated()); 208 | } 209 | 210 | // position at top of current cell descending by font height - font 211 | // descent, because we are positioning the base line here 212 | float cursorY = yStart - getTopPadding() - FontUtils.getHeight(getFont(), getFontSize()) 213 | - FontUtils.getDescent(getFont(), getFontSize()) - (getTopBorder() == null ? 0 : getTopBorder().getWidth()); 214 | float cursorX = xStart; 215 | 216 | // loop through tokens 217 | for (Map.Entry> entry : paragraph.getMapLineTokens().entrySet()) { 218 | 219 | // calculate the width of this line 220 | float freeSpaceWithinLine = paragraph.getMaxLineWidth() - paragraph.getLineWidth(entry.getKey()); 221 | if (isTextRotated()) { 222 | switch (align) { 223 | case CENTER: 224 | cursorY += freeSpaceWithinLine / 2; 225 | break; 226 | case LEFT: 227 | break; 228 | case RIGHT: 229 | cursorY += freeSpaceWithinLine; 230 | break; 231 | } 232 | } else { 233 | switch (align) { 234 | case CENTER: 235 | cursorX += freeSpaceWithinLine / 2; 236 | break; 237 | case LEFT: 238 | // it doesn't matter because X position is always the same 239 | // as row above 240 | break; 241 | case RIGHT: 242 | cursorX += freeSpaceWithinLine; 243 | break; 244 | } 245 | } 246 | 247 | // iterate through tokens in current line 248 | PDFont currentFont = paragraph.getFont(false, false); 249 | for (Token token : entry.getValue()) { 250 | switch (token.getType()) { 251 | case OPEN_TAG: 252 | if ("b".equals(token.getData())) { 253 | boldCounter++; 254 | } else if ("i".equals(token.getData())) { 255 | italicCounter++; 256 | } 257 | break; 258 | case CLOSE_TAG: 259 | if ("b".equals(token.getData())) { 260 | boldCounter = Math.max(boldCounter - 1, 0); 261 | } else if ("i".equals(token.getData())) { 262 | italicCounter = Math.max(italicCounter - 1, 0); 263 | } 264 | break; 265 | case PADDING: 266 | cursorX += Float.parseFloat(token.getData()); 267 | break; 268 | case ORDERING: 269 | currentFont = paragraph.getFont(boldCounter > 0, italicCounter > 0); 270 | tableCellContentStream.setFont(currentFont, getFontSize()); 271 | if (isTextRotated()) { 272 | // if it is not calculation then draw it 273 | if (!onlyCalculateHeight) { 274 | tableCellContentStream.newLineAt(cursorX, cursorY); 275 | tableCellContentStream.showText(token.getData()); 276 | } 277 | cursorY += token.getWidth(currentFont) / 1000 * getFontSize(); 278 | } else { 279 | // if it is not calculation then draw it 280 | if (!onlyCalculateHeight) { 281 | tableCellContentStream.newLineAt(cursorX, cursorY); 282 | tableCellContentStream.showText(token.getData()); 283 | } 284 | cursorX += token.getWidth(currentFont) / 1000 * getFontSize(); 285 | } 286 | break; 287 | case BULLET: 288 | float widthOfSpace = currentFont.getSpaceWidth(); 289 | float halfHeight = FontUtils.getHeight(currentFont, getFontSize()) / 2; 290 | if (isTextRotated()) { 291 | if (!onlyCalculateHeight) { 292 | PDStreamUtils.rect(tableCellContentStream, cursorX + halfHeight, cursorY, 293 | token.getWidth(currentFont) / 1000 * getFontSize(), 294 | widthOfSpace / 1000 * getFontSize(), getTextColor()); 295 | } 296 | // move cursorY for two characters (one for bullet, one 297 | // for space after bullet) 298 | cursorY += 2 * widthOfSpace / 1000 * getFontSize(); 299 | } else { 300 | if (!onlyCalculateHeight) { 301 | PDStreamUtils.rect(tableCellContentStream, cursorX, cursorY + halfHeight, 302 | token.getWidth(currentFont) / 1000 * getFontSize(), 303 | widthOfSpace / 1000 * getFontSize(), getTextColor()); 304 | } 305 | // move cursorX for two characters (one for bullet, one 306 | // for space after bullet) 307 | cursorX += 2 * widthOfSpace / 1000 * getFontSize(); 308 | } 309 | break; 310 | case TEXT: 311 | currentFont = paragraph.getFont(boldCounter > 0, italicCounter > 0); 312 | tableCellContentStream.setFont(currentFont, getFontSize()); 313 | if (isTextRotated()) { 314 | if (!onlyCalculateHeight) { 315 | tableCellContentStream.newLineAt(cursorX, cursorY); 316 | tableCellContentStream.showText(token.getData()); 317 | } 318 | cursorY += token.getWidth(currentFont) / 1000 * getFontSize(); 319 | } else { 320 | if (!onlyCalculateHeight) { 321 | tableCellContentStream.newLineAt(cursorX, cursorY); 322 | tableCellContentStream.showText(token.getData()); 323 | } 324 | cursorX += token.getWidth(currentFont) / 1000 * getFontSize(); 325 | } 326 | break; 327 | } 328 | } 329 | // reset 330 | cursorX = xStart; 331 | cursorY -= FontUtils.getHeight(getFont(), getFontSize()); 332 | } 333 | return cursorY; 334 | } 335 | 336 | /** 337 | *

338 | * This method draw table cell with proper X,Y position which are determined 339 | * in {@link Table#draw()} method 340 | *

341 | *

342 | * NOTE: if entire row is not header row then use bold instead header cell ( 343 | * {@code 344 | * 345 | }) 346 | *

347 | * 348 | * @param page 349 | * {@link PDPage} where table cell be written on 350 | * 351 | */ 352 | @SuppressWarnings({ "unused", "unchecked" }) 353 | public void draw(PDPage page) { 354 | try { 355 | // please consider the cell's paddings 356 | float tableWidth = this.width - getLeftPadding() - getRightPadding(); 357 | tableCellContentStream = new PageContentStreamOptimized(new PDPageContentStream(doc, page, 358 | PDPageContentStream.AppendMode.APPEND, true)); 359 | // check if there is some additional text outside inner table 360 | String[] outerTableText = tableData.split(""); 372 | for (String chunkie : chunks) { 373 | if (chunkie.contains("} 32 | */ 33 | public class DataTable { 34 | public static final Boolean HASHEADER = true; 35 | public static final Boolean NOHEADER = false; 36 | private Table table; 37 | private List colWidths; 38 | private final Cell headerCellTemplate; 39 | private final List dataCellTemplateEvenList = new ArrayList<>(); 40 | private final List dataCellTemplateOddList = new ArrayList<>(); 41 | private final Cell defaultCellTemplate; 42 | private boolean copyFirstColumnCellTemplateOddToEven = false; 43 | private boolean copyLastColumnCellTemplateOddToEven = false; 44 | private UpdateCellProperty updateCellProperty = null; 45 | 46 | /** 47 | *

48 | * Create a CSVTable object to be able to add CSV document to a Table. A 49 | * page needs to be passed to the constructor so the Template Cells can be 50 | * created. 51 | *

52 | * 53 | * @param table {@link Table} 54 | * @param page {@link PDPage} 55 | * @throws IOException If there is an error releasing resources 56 | */ 57 | public DataTable(Table table, PDPage page) throws IOException { 58 | this(table, page, new ArrayList(),null); 59 | } 60 | 61 | /** 62 | *

63 | * Create a CSVTable object to be able to add CSV document to a Table. A 64 | * page needs to be passed to the constructor so the Template Cells can be 65 | * created.The interface allows you to update the cell property 66 | *

67 | * 68 | * @param table {@link Table} 69 | * @param page {@link PDPage} 70 | * @param updateCellProperty {@link UpdateCellProperty} 71 | * @throws IOException If there is an error releasing resources 72 | */ 73 | public DataTable(Table table, PDPage page, UpdateCellProperty updateCellProperty) throws IOException { 74 | this(table, page, new ArrayList(), updateCellProperty); 75 | } 76 | 77 | /** 78 | *

79 | * Create a CSVTable object to be able to add CSV document to a Table. A 80 | * page needs to be passed to the constructor so the Template Cells can be 81 | * created. The column widths can be given 82 | *

83 | * 84 | * @param table {@link Table} 85 | * @param page {@link PDPage} 86 | * @param colWidths column widths 87 | * @throws IOException If there is an error releasing resources 88 | */ 89 | public DataTable(Table table, PDPage page, List colWidths) throws IOException { 90 | this(table, page, colWidths, null); 91 | } 92 | 93 | /** 94 | *

95 | * Create a CSVTable object to be able to add CSV document to a Table. A 96 | * page needs to be passed to the constructor so the Template Cells can be 97 | * created. The column widths can be given and an interface allows you to update the cell property 98 | *

99 | * 100 | * @param table {@link Table} 101 | * @param page {@link PDPage} 102 | * @param colWidths column widths 103 | * @param updateCellProperty {@link UpdateCellProperty} 104 | * @throws IOException If there is an error releasing resources 105 | */ 106 | public DataTable(Table table, PDPage page, List colWidths, UpdateCellProperty updateCellProperty) throws IOException { 107 | this.table = table; 108 | this.colWidths = (colWidths.size() == 0) ? null : colWidths; 109 | this.updateCellProperty = updateCellProperty; 110 | // Create a dummy pdf document, page and table to create template cells 111 | PDDocument ddoc = new PDDocument(); 112 | PDPage dpage = new PDPage(); 113 | dpage.setMediaBox(page.getMediaBox()); 114 | dpage.setRotation(page.getRotation()); 115 | ddoc.addPage(dpage); 116 | BaseTable dummyTable = new BaseTable(10f, 10f, 10f, table.getWidth(), 10f, ddoc, dpage, false, false); 117 | Row dr = dummyTable.createRow(0f); 118 | headerCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.CENTER, VerticalAlignment.MIDDLE); 119 | if (this.colWidths == null) { 120 | dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 121 | dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 122 | dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 123 | dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 124 | dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 125 | dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 126 | } else { 127 | for (int i = 0 ; i < this.colWidths.size(); i++) { 128 | dataCellTemplateEvenList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 129 | dataCellTemplateOddList.add(dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE)); 130 | } 131 | } 132 | defaultCellTemplate = dr.createCell(10f, "A", HorizontalAlignment.LEFT, VerticalAlignment.MIDDLE); 133 | setDefaultStyles(); 134 | ddoc.close(); 135 | } 136 | 137 | /** 138 | *

139 | * Default cell styles for all cells. By default, only the header cell has a 140 | * different style than the rest of the table. 141 | *

142 | */ 143 | private void setDefaultStyles() { 144 | LineStyle thinline = new LineStyle(Color.BLACK, 0.75f); 145 | // Header style 146 | headerCellTemplate.setFillColor(new Color(137, 218, 245)); 147 | headerCellTemplate.setTextColor(Color.BLACK); 148 | headerCellTemplate.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD)); 149 | headerCellTemplate.setBorderStyle(thinline); 150 | 151 | // Normal cell style, all rows and columns are the same by default 152 | defaultCellTemplate.setFillColor(new Color(242, 242, 242)); 153 | defaultCellTemplate.setTextColor(Color.BLACK); 154 | defaultCellTemplate.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA)); 155 | defaultCellTemplate.setBorderStyle(thinline); 156 | Iterator iterator = dataCellTemplateEvenList.iterator(); 157 | while (iterator.hasNext()){ 158 | iterator.next().copyCellStyle(defaultCellTemplate); 159 | } 160 | iterator = dataCellTemplateOddList.iterator(); 161 | while (iterator.hasNext()){ 162 | iterator.next().copyCellStyle(defaultCellTemplate); 163 | } 164 | } 165 | 166 | /** 167 | *

168 | * Get the table to add the csv content to 169 | *

170 | * 171 | * @return {@link Table} 172 | */ 173 | public Table getTable() { 174 | return table; 175 | } 176 | 177 | /** 178 | *

179 | * Set the Table that the CSV document will be added to 180 | *

181 | * 182 | * @param table {@link Table} 183 | */ 184 | public void setTable(Table table) { 185 | this.table = table; 186 | } 187 | 188 | /** 189 | *

190 | * return the column widths if provided otherwise null 191 | *

192 | * 193 | * @return column widths 194 | */ 195 | public List getColWidths() { 196 | return colWidths; 197 | } 198 | 199 | /** 200 | *

201 | * Set the column widths 202 | *

203 | * 204 | * @param colWidths 205 | */ 206 | public void setColWidths(List colWidths) { 207 | this.colWidths = colWidths; 208 | } 209 | 210 | /** 211 | *

212 | * Get the Cell Template that will be applied to header cells. 213 | *

214 | * 215 | * @return header {@link Cell}'s template 216 | */ 217 | public Cell getHeaderCellTemplate() { 218 | return headerCellTemplate; 219 | } 220 | 221 | /** 222 | *

223 | * Get the Cell Template that will be assigned to Data cells that are in 224 | * even rows, and are not the first or last column 225 | *

226 | * 227 | * @return data {@link Cell}'s template 228 | */ 229 | @Deprecated 230 | public Cell getDataCellTemplateEven() { 231 | return dataCellTemplateEvenList.get(1); 232 | } 233 | 234 | /** 235 | *

236 | * Get the Cell Template that will be assigned to Data cells that are in odd 237 | * rows, and are not the first or last column 238 | *

239 | * 240 | * @return data {@link Cell}'s template 241 | */ 242 | @Deprecated 243 | public Cell getDataCellTemplateOdd() { 244 | return dataCellTemplateOddList.get(1); 245 | } 246 | 247 | /** 248 | *

249 | * Get the Cell Templates that will be assigned to Data cells that are in even 250 | * rows, and it contain first and last column. 251 | * By default dataCellTemplateEvenList.get(1) will be used for all data even cells 252 | *

253 | * 254 | * @return data {@link Cell}'s template 255 | */ 256 | public List getDataCellTemplateEvenList() { 257 | return dataCellTemplateEvenList; 258 | } 259 | 260 | /** 261 | *

262 | * Get the Cell Templates that will be assigned to Data cells that are in odd 263 | * rows, and it contain first and last column. 264 | * By default dataCellTemplateOddList.get(1) will be used for all data odd cells 265 | *

266 | * 267 | * @return data {@link Cell}'s template 268 | */ 269 | public List getDataCellTemplateOddList() { 270 | return dataCellTemplateOddList; 271 | } 272 | 273 | /** 274 | *

275 | * Get the Cell Template that will be assigned to cells in the first column 276 | *

277 | * 278 | * @return {@link Cell}'s template 279 | */ 280 | public Cell getFirstColumnCellTemplate() { 281 | copyFirstColumnCellTemplateOddToEven =true; 282 | return dataCellTemplateOddList.get(0); 283 | } 284 | 285 | /** 286 | *

287 | * Get the Cell Template that will be assigned to cells in the last columns 288 | *

289 | * 290 | * @return {@link Cell}'s template 291 | */ 292 | public Cell getLastColumnCellTemplate() { 293 | copyLastColumnCellTemplateOddToEven =true; 294 | return dataCellTemplateOddList.get(dataCellTemplateOddList.size()-1); 295 | } 296 | 297 | /** 298 | *

299 | * Get the Cell Template that will be assigned to cells in the first column that are in 300 | * odd rows 301 | *

302 | * 303 | * @return {@link Cell}'s template 304 | */ 305 | public Cell getFirstColumnCellTemplateOdd() { 306 | copyFirstColumnCellTemplateOddToEven = false; 307 | return dataCellTemplateOddList.get(0); 308 | } 309 | 310 | /** 311 | *

312 | * Get the Cell Template that will be assigned to cells in the last columns that are in 313 | * odd rows 314 | *

315 | * 316 | * @return {@link Cell}'s template 317 | */ 318 | public Cell getLastColumnCellTemplateOdd() { 319 | copyLastColumnCellTemplateOddToEven = false; 320 | return dataCellTemplateOddList.get(dataCellTemplateOddList.size()-1); 321 | } 322 | 323 | /** 324 | *

325 | * Get the Cell Template that will be assigned to cells in the first column that are in 326 | * even rows 327 | *

328 | * 329 | * @return {@link Cell}'s template 330 | */ 331 | public Cell getFirstColumnCellTemplateEven() { 332 | copyFirstColumnCellTemplateOddToEven = false; 333 | return dataCellTemplateEvenList.get(0); 334 | } 335 | 336 | /** 337 | *

338 | * Get the Cell Template that will be assigned to cells in the last columns that are in 339 | * even rows 340 | * 341 | * @return {@link Cell}'s template 342 | */ 343 | public Cell getLastColumnCellTemplateEven() { 344 | copyLastColumnCellTemplateOddToEven = false; 345 | return dataCellTemplateEvenList.get(dataCellTemplateOddList.size()-1); 346 | } 347 | 348 | /** 349 | *

350 | * Add a List of Lists to the Table 351 | *

352 | * 353 | * @param data {@link Table}'s data 354 | * @param hasHeader boolean if {@link Table} has header 355 | * @throws IOException parsing error 356 | */ 357 | public void addListToTable(List data, Boolean hasHeader) throws IOException { 358 | if (data == null || data.isEmpty()) { 359 | return; 360 | } 361 | StringBuilder output = new StringBuilder(); 362 | char separator = ';'; 363 | 364 | // Convert Map of arbitrary objects to a csv String 365 | for (List inputList : data) { 366 | StringBuilder row = new StringBuilder(); 367 | for (Object v : inputList) { 368 | String value = v.toString(); 369 | if (value.contains("" + separator)) { 370 | // surround value with quotes if it contains the escape 371 | // character 372 | value = "\"" + value + "\""; 373 | } 374 | value = value.replaceAll("\n", "
"); 375 | row.append(value).append(separator); 376 | } 377 | // remove the last separator 378 | row = new StringBuilder(row.substring(0, row.length() - 1)); 379 | output.append(row).append("\n"); 380 | } 381 | 382 | addCsvToTable(output.toString(), hasHeader, separator); 383 | } 384 | 385 | /** 386 | *

387 | * Add a String representing a CSV document to the Table 388 | *

389 | * 390 | * @param data {@link Table}'s data 391 | * @param hasHeader boolean if {@link Table} has header 392 | * @param separator {@code char} on which data will be parsed 393 | * @throws IOException parsing error 394 | */ 395 | public void addCsvToTable(String data, Boolean hasHeader, char separator) throws IOException { 396 | Iterable records = CSVParser.parse(data, CSVFormat.EXCEL.withDelimiter(separator)); 397 | Boolean isHeader = hasHeader; 398 | Boolean isFirst = true; 399 | Boolean odd = true; 400 | Map colWidths = new HashMap<>(); 401 | int numcols = 0; 402 | int numrow = 0; 403 | for (CSVRecord line : records) { 404 | 405 | if (isFirst) { 406 | 407 | // calculate the width of the columns 408 | float totalWidth = 0.0f; 409 | if (this.colWidths == null) { 410 | 411 | for (int i = 0; i < line.size(); i++) { 412 | String cellValue = line.get(i); 413 | float textWidth = FontUtils.getStringWidth(headerCellTemplate.getFont(), " " + cellValue + " ", 414 | headerCellTemplate.getFontSize()); 415 | totalWidth += textWidth; 416 | numcols = i; 417 | } 418 | // totalWidth has the total width we need to have all 419 | // columns 420 | // full sized. 421 | // calculate a factor to reduce/increase size by to make it 422 | // fit 423 | // in our table 424 | float sizefactor = table.getWidth() / totalWidth; 425 | for (int i = 0; i <= numcols; i++) { 426 | String cellValue = ""; 427 | if (line.size() >= i) { 428 | cellValue = line.get(i); 429 | } 430 | float textWidth = FontUtils.getStringWidth(headerCellTemplate.getFont(), " " + cellValue + " ", 431 | headerCellTemplate.getFontSize()); 432 | float widthPct = textWidth * 100 / table.getWidth(); 433 | // apply width factor 434 | widthPct = widthPct * sizefactor; 435 | colWidths.put(i, widthPct); 436 | } 437 | } else { 438 | for (Float width : this.colWidths){ 439 | totalWidth += width; 440 | } 441 | for (int i = 0; i < this.colWidths.size(); i++) { 442 | // to 443 | // percent 444 | colWidths.put(i,this.colWidths.get(i) / (totalWidth / 100)); 445 | numcols = i; 446 | } 447 | 448 | } 449 | updateTemplateList(line.size()); 450 | isFirst = false; 451 | } 452 | if (isHeader) { 453 | // Add Header Row 454 | Row h = table.createRow(headerCellTemplate.getCellHeight()); 455 | for (int i = 0; i <= numcols; i++) { 456 | String cellValue = line.get(i); 457 | Cell c = h.createCell(colWidths.get(i), cellValue, headerCellTemplate.getAlign(), 458 | headerCellTemplate.getValign()); 459 | // Apply style of header cell to this cell 460 | c.copyCellStyle(headerCellTemplate); 461 | c.setText(cellValue); 462 | } 463 | table.addHeaderRow(h); 464 | isHeader = false; 465 | } else { 466 | Row r = table.createRow(dataCellTemplateEvenList.get(0).getCellHeight()); 467 | for (int i = 0; i <= numcols; i++) { 468 | // Choose the correct template for the cell 469 | Cell template = dataCellTemplateEvenList.get(i); 470 | if (odd) { 471 | template = dataCellTemplateOddList.get(i);; 472 | } 473 | 474 | String cellValue = ""; 475 | if (line.size() >= i) { 476 | cellValue = line.get(i); 477 | } 478 | Cell c = r.createCell(colWidths.get(i), cellValue, template.getAlign(), template.getValign()); 479 | // Apply style of header cell to this cell 480 | c.copyCellStyle(template); 481 | c.setText(cellValue); 482 | if (updateCellProperty != null) 483 | updateCellProperty.updateCellPropertiesAtColumn(c,i,numrow); 484 | } 485 | numrow++; 486 | } 487 | odd = !odd; 488 | } 489 | } 490 | 491 | private void updateTemplateList(int size) { 492 | if (copyFirstColumnCellTemplateOddToEven) 493 | dataCellTemplateEvenList.set(0, dataCellTemplateOddList.get(0)); 494 | if (copyLastColumnCellTemplateOddToEven) 495 | dataCellTemplateEvenList.set(dataCellTemplateEvenList.size()-1, dataCellTemplateOddList.get(dataCellTemplateOddList.size()-1)); 496 | if (size <= 3) return; // Only in case of more than 3 columns there are first last and data template 497 | while (dataCellTemplateEvenList.size() < size) dataCellTemplateEvenList.add(1,dataCellTemplateEvenList.get(1) ); 498 | while (dataCellTemplateOddList.size() < size) dataCellTemplateOddList.add(1,dataCellTemplateOddList.get(1) ); 499 | while (dataCellTemplateEvenList.size() > size) dataCellTemplateEvenList.remove(dataCellTemplateEvenList.size()-2 ); 500 | while (dataCellTemplateOddList.size() >size) dataCellTemplateOddList.remove(dataCellTemplateOddList.size()-2 ); 501 | 502 | } 503 | } -------------------------------------------------------------------------------- /src/test/java/be/quodlibet/boxable/DataTableTest.java: -------------------------------------------------------------------------------- 1 | package be.quodlibet.boxable; 2 | 3 | import be.quodlibet.boxable.datatable.DataTable; 4 | import be.quodlibet.boxable.datatable.UpdateCellProperty; 5 | import com.google.common.io.Files; 6 | import java.awt.Color; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.net.URL; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | 16 | import org.apache.commons.io.IOUtils; 17 | import org.apache.pdfbox.pdmodel.PDDocument; 18 | import org.apache.pdfbox.pdmodel.PDPage; 19 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 20 | import org.junit.After; 21 | import org.junit.AfterClass; 22 | import org.junit.Before; 23 | import org.junit.BeforeClass; 24 | import org.junit.Test; 25 | 26 | /** 27 | * 28 | * @author Dries Horions 29 | */ 30 | public class DataTableTest 31 | { 32 | 33 | public DataTableTest() 34 | { 35 | } 36 | 37 | @BeforeClass 38 | public static void setUpClass() 39 | { 40 | } 41 | 42 | @AfterClass 43 | public static void tearDownClass() 44 | { 45 | } 46 | 47 | @Before 48 | public void setUp() 49 | { 50 | } 51 | 52 | @After 53 | public void tearDown() 54 | { 55 | } 56 | 57 | @Test 58 | public void listTestLandscape() throws IOException 59 | { 60 | 61 | 62 | 63 | //Initialize Document 64 | PDDocument doc = new PDDocument(); 65 | PDPage page = new PDPage(); 66 | //Create a landscape page 67 | page.setMediaBox(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); 68 | doc.addPage(page); 69 | //Initialize table 70 | float margin = 10; 71 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 72 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 73 | float yStart = yStartNewPage; 74 | float bottomMargin = 0; 75 | 76 | //Create the data 77 | List data = new ArrayList<>(); 78 | data.add(new ArrayList<>( 79 | Arrays.asList("Column One", "Column Two", "Column Three", "Column Four", "Column Five"))); 80 | for (int i = 1; i <= 100; i++) { 81 | data.add(new ArrayList<>( 82 | Arrays.asList("Row " + i + " Col One", "Row " + i + " Col Two", "Row " + i + " Col Three", "Row " + i + " Col Four", "Row " + i + " Col Five"))); 83 | } 84 | 85 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 86 | true); 87 | DataTable t = new DataTable(dataTable, page); 88 | t.addListToTable(data, DataTable.HASHEADER); 89 | dataTable.draw(); 90 | File file = new File("target/ListExampleLandscape.pdf"); 91 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 92 | file.getParentFile().mkdirs(); 93 | doc.save(file); 94 | doc.close(); 95 | } 96 | 97 | @Test 98 | public void csvTestColWidths() throws IOException 99 | { 100 | String data1 = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/teknologic.csv"); 101 | String data = "Discription;narrow;this is wider;and this one is even wider\n"+ 102 | "The length of the column widths is given by the constructor; use it; break it; fix it\n"+ 103 | "Snap it; work it; quick; erase it\n"+ 104 | "Write it; cut it; paste it; save it\n"; 105 | //Initialize Document 106 | PDDocument doc = new PDDocument(); 107 | PDPage page = new PDPage(); 108 | //Create a landscape page 109 | page.setMediaBox(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); 110 | doc.addPage(page); 111 | //Initialize table 112 | float margin = 10; 113 | float tablesmargin = 50; 114 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 115 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 116 | float yStart = yStartNewPage; 117 | float bottomMargin = 20; 118 | 119 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 120 | true); 121 | DataTable t = new DataTable(dataTable, page, Arrays.asList(3f,1f,1f,1f)); 122 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 123 | yStart = dataTable.draw() - tablesmargin; 124 | 125 | if (dataTable.getCurrentPage() != page) { 126 | page = dataTable.getCurrentPage(); 127 | } 128 | 129 | BaseTable dataTable1 = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 130 | true); 131 | DataTable t1 = new DataTable(dataTable1, page); 132 | t1.addCsvToTable(data1, DataTable.HASHEADER, ';'); 133 | dataTable1.draw(); 134 | 135 | File file = new File("target/CSVexampleColWidths.pdf"); 136 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 137 | file.getParentFile().mkdirs(); 138 | doc.save(file); 139 | doc.close(); 140 | } 141 | 142 | @Test 143 | public void csvTestPortrait() throws IOException 144 | { 145 | String data = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/Eurostat_Immigration_Applications.csv"); 146 | //Initialize Document 147 | PDDocument doc = new PDDocument(); 148 | PDPage page = new PDPage(); 149 | doc.addPage(page); 150 | //Initialize table 151 | float margin = 10; 152 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 153 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 154 | float yStart = yStartNewPage; 155 | float bottomMargin = 0; 156 | 157 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 158 | true); 159 | DataTable t = new DataTable(dataTable, page); 160 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 161 | dataTable.draw(); 162 | 163 | File file = new File("target/CSVexamplePortrait.pdf"); 164 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 165 | file.getParentFile().mkdirs(); 166 | doc.save(file); 167 | doc.close(); 168 | } 169 | 170 | @Test 171 | public void csvTestLandscape() throws IOException 172 | { 173 | String data = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/Eurostat_Immigration_Applications.csv"); 174 | //Initialize Document 175 | PDDocument doc = new PDDocument(); 176 | PDPage page = new PDPage(); 177 | //Create a landscape page 178 | page.setMediaBox(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); 179 | doc.addPage(page); 180 | //Initialize table 181 | float margin = 10; 182 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 183 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 184 | float yStart = yStartNewPage; 185 | float bottomMargin = 0; 186 | 187 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 188 | true); 189 | DataTable t = new DataTable(dataTable, page); 190 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 191 | dataTable.draw(); 192 | File file = new File("target/CSVexampleLandscape.pdf"); 193 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 194 | file.getParentFile().mkdirs(); 195 | doc.save(file); 196 | doc.close(); 197 | } 198 | 199 | @Test 200 | public void csvTestSimple() throws IOException 201 | { 202 | 203 | String data = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/Eurostat_Energcy_Prices_Medium_Household.csv"); 204 | //Initialize Document 205 | PDDocument doc = new PDDocument(); 206 | PDPage page = new PDPage(); 207 | //Create a landscape page 208 | page.setMediaBox(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); 209 | doc.addPage(page); 210 | //Initialize table 211 | float margin = 10; 212 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 213 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 214 | float yStart = yStartNewPage; 215 | float bottomMargin = 0; 216 | 217 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 218 | true); 219 | //Add a few things to the table that's not coming from the csv file 220 | Row h1 = dataTable.createRow(0f); 221 | Cell c1 = h1.createCell(100, "Electricity Prices by type of user"); 222 | c1.setFillColor(new Color(144, 195, 212)); 223 | dataTable.addHeaderRow(h1); 224 | Row h2 = dataTable.createRow(0f); 225 | Cell c2 = h2.createCell(100, "Eur per kWh for Medium Size Households.
Source http://ec.europa.eu/eurostat/tgm/table.do?tab=table&init=1&plugin=1&language=en&pcode=ten00117"); 226 | c2.setFillColor(new Color(175, 212, 224)); 227 | dataTable.addHeaderRow(h2); 228 | DataTable t = new DataTable(dataTable, page); 229 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 230 | dataTable.draw(); 231 | File file = new File("target/CSVexampleSimple.pdf"); 232 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 233 | file.getParentFile().mkdirs(); 234 | doc.save(file); 235 | doc.close(); 236 | 237 | } 238 | 239 | @Test 240 | public void csvTestAdvanced() throws IOException 241 | { 242 | 243 | String data = readData("https://s3.amazonaws.com/misc.quodlibet.be/Boxable/Eurostat_Energcy_Prices_Medium_Household.csv"); 244 | //Initialize Document 245 | PDDocument doc = new PDDocument(); 246 | PDPage page = new PDPage(); 247 | //Create a landscape page 248 | page.setMediaBox(new PDRectangle(PDRectangle.A4.getHeight(), PDRectangle.A4.getWidth())); 249 | doc.addPage(page); 250 | //Initialize table 251 | float margin = 10; 252 | float tablesmargin = 50; 253 | float tableWidth = page.getMediaBox().getWidth() - (2 * margin); 254 | float yStartNewPage = page.getMediaBox().getHeight() - (2 * margin); 255 | float yStart = yStartNewPage; 256 | float bottomMargin = 0; 257 | 258 | BaseTable dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 259 | true); 260 | //Add a few things to the table that's not coming from the csv file 261 | Row h1 = dataTable.createRow(0f); 262 | Cell c1 = h1.createCell(100, "Electricity Prices by type of user"); 263 | c1.setFillColor(new Color(144, 195, 212)); 264 | dataTable.addHeaderRow(h1); 265 | Row h2 = dataTable.createRow(0f); 266 | Cell c2 = h2.createCell(100, "Eur per kWh for Medium Size Households.
Source http://ec.europa.eu/eurostat/tgm/table.do?tab=table&init=1&plugin=1&language=en&pcode=ten00117"); 267 | c2.setFillColor(new Color(175, 212, 224)); 268 | dataTable.addHeaderRow(h2); 269 | DataTable t = new DataTable(dataTable, page); 270 | //set the style template for header cells 271 | t.getHeaderCellTemplate().setFillColor(new Color(13, 164, 214)); 272 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 273 | yStart = dataTable.draw() - tablesmargin; 274 | 275 | if (dataTable.getCurrentPage() != page) { 276 | page = dataTable.getCurrentPage(); 277 | } 278 | 279 | // Next Table with other design 280 | 281 | dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 282 | true); 283 | h1 = dataTable.createRow(0f); 284 | c1 = h1.createCell(100, "Same tabel with zebra design"); 285 | c1.setFillColor(new Color(144, 195, 212)); 286 | dataTable.addHeaderRow(h1); 287 | t = new DataTable(dataTable, page); 288 | Iterator iterator = t.getDataCellTemplateEvenList().iterator(); 289 | while (iterator.hasNext()){ 290 | iterator.next().setFillColor(Color.WHITE); 291 | } 292 | iterator = t.getDataCellTemplateOddList().iterator(); 293 | while (iterator.hasNext()){ 294 | iterator.next().setFillColor(new Color(250, 242, 242)); 295 | } 296 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 297 | yStart = dataTable.draw() - tablesmargin; 298 | 299 | if (dataTable.getCurrentPage() != page) { 300 | page = dataTable.getCurrentPage(); 301 | } 302 | 303 | // Next Table with other design 304 | 305 | dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 306 | true); 307 | h1 = dataTable.createRow(0f); 308 | c1 = h1.createCell(100, "Same tabel with zebra design and first and last column with other collors"); 309 | c1.setFillColor(new Color(144, 195, 212)); 310 | dataTable.addHeaderRow(h1); 311 | t = new DataTable(dataTable, page); 312 | iterator = t.getDataCellTemplateEvenList().iterator(); 313 | while (iterator.hasNext()){ 314 | iterator.next().setFillColor(new Color(250, 242, 242)); 315 | } 316 | iterator = t.getDataCellTemplateOddList().iterator(); 317 | while (iterator.hasNext()){ 318 | iterator.next().setFillColor(new Color(250, 242, 242)); 319 | } 320 | //set the style template for first column 321 | t.getFirstColumnCellTemplate().setFillColor(new Color(13, 164, 214)); 322 | //set the style template for last column 323 | t.getLastColumnCellTemplate().setFillColor(new Color(144, 195, 212)); 324 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 325 | yStart = dataTable.draw() - tablesmargin; 326 | 327 | if (dataTable.getCurrentPage() != page) { 328 | page = dataTable.getCurrentPage(); 329 | } 330 | 331 | // Next Table with other design 332 | 333 | dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 334 | true); 335 | h1 = dataTable.createRow(0f); 336 | c1 = h1.createCell(100, "Same tabel with zebra design and first and last column with other collors with also zebra design"); 337 | c1.setFillColor(new Color(144, 195, 212)); 338 | dataTable.addHeaderRow(h1); 339 | t = new DataTable(dataTable, page); 340 | iterator = t.getDataCellTemplateEvenList().iterator(); 341 | while (iterator.hasNext()){ 342 | iterator.next().setFillColor(new Color(250, 242, 242)); 343 | } 344 | iterator = t.getDataCellTemplateOddList().iterator(); 345 | while (iterator.hasNext()){ 346 | iterator.next().setFillColor(new Color(250, 242, 242)); 347 | } 348 | //set the style template for first column odd 349 | t.getFirstColumnCellTemplateOdd().setFillColor(new Color(13, 164, 214)); 350 | //set the style template for first column even 351 | t.getFirstColumnCellTemplateEven().setFillColor(new Color(23, 174, 224)); 352 | //set the style template for last column odd 353 | t.getLastColumnCellTemplateOdd().setFillColor(new Color(144, 195, 212)); 354 | //set the style template for last column even 355 | t.getLastColumnCellTemplateEven().setFillColor(new Color(134, 205, 222)); 356 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 357 | yStart = dataTable.draw() - tablesmargin; 358 | 359 | if (dataTable.getCurrentPage() != page) { 360 | page = dataTable.getCurrentPage(); 361 | } 362 | 363 | // Next Table with other design 364 | 365 | dataTable = new BaseTable(yStart, yStartNewPage, bottomMargin, tableWidth, margin, doc, page, true, 366 | true); 367 | h1 = dataTable.createRow(0f); 368 | c1 = h1.createCell(100, "Same tabel different alignment and colored data cells"); 369 | c1.setFillColor(new Color(144, 195, 212)); 370 | dataTable.addHeaderRow(h1); 371 | final Color c01 = new Color(160, 174, 224); 372 | final Color c02 = new Color(23, 174, 224); 373 | t = new DataTable(dataTable, page,new UpdateCellProperty() { 374 | 375 | @Override 376 | public void updateCellPropertiesAtColumn(Cell c, int column, int row) { 377 | if (column == 11){ 378 | if(c.getText().startsWith("0,2")) 379 | c.setFillColor(c02); 380 | if (c.getText().startsWith("0,1")) 381 | c.setFillColor(c01); 382 | } 383 | } 384 | }); 385 | iterator = t.getDataCellTemplateEvenList().iterator(); 386 | while (iterator.hasNext()){ 387 | iterator.next().setAlign(HorizontalAlignment.RIGHT); 388 | } 389 | iterator = t.getDataCellTemplateOddList().iterator(); 390 | while (iterator.hasNext()){ 391 | iterator.next().setAlign(HorizontalAlignment.RIGHT); 392 | } 393 | //set the style template for first column back to left 394 | t.getFirstColumnCellTemplate().setAlign(HorizontalAlignment.LEFT); 395 | t.addCsvToTable(data, DataTable.HASHEADER, ';'); 396 | yStart = dataTable.draw() - tablesmargin; 397 | 398 | if (dataTable.getCurrentPage() != page) { 399 | page = dataTable.getCurrentPage(); 400 | } 401 | 402 | File file = new File("target/CSVexampleAdvanced.pdf"); 403 | System.out.println("Sample file saved at : " + file.getAbsolutePath()); 404 | file.getParentFile().mkdirs(); 405 | doc.save(file); 406 | doc.close(); 407 | } 408 | 409 | private static String readData(String url) 410 | { 411 | InputStream in = null; 412 | try { 413 | in = new URL(url).openStream(); 414 | return IOUtils.toString(in); 415 | } 416 | catch (IOException ex) { 417 | System.out.println(ex.getMessage()); 418 | } 419 | finally { 420 | IOUtils.closeQuietly(in); 421 | } 422 | return ""; 423 | } 424 | 425 | private static PDPage addNewPage(PDDocument doc) 426 | { 427 | PDPage page = new PDPage(); 428 | doc.addPage(page); 429 | return page; 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /src/main/java/be/quodlibet/boxable/Cell.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | Quodlibet.be 4 | */ 5 | package be.quodlibet.boxable; 6 | 7 | import java.awt.Color; 8 | import java.net.URL; 9 | import java.io.IOException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import org.apache.pdfbox.pdmodel.PDDocument; 14 | import org.apache.pdfbox.pdmodel.PDPage; 15 | import org.apache.pdfbox.pdmodel.common.PDRectangle; 16 | import org.apache.pdfbox.pdmodel.font.PDFont; 17 | import org.apache.pdfbox.pdmodel.font.PDType1Font; 18 | 19 | import be.quodlibet.boxable.line.LineStyle; 20 | import be.quodlibet.boxable.text.WrappingFunction; 21 | import be.quodlibet.boxable.utils.FontUtils; 22 | import org.apache.pdfbox.pdmodel.font.Standard14Fonts; 23 | 24 | public class Cell { 25 | 26 | private float width; 27 | private Float height; 28 | private String text; 29 | 30 | private URL url = null; 31 | 32 | private PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA); 33 | private PDFont fontBold = new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD); 34 | 35 | private float fontSize = 8; 36 | private Color fillColor; 37 | private Color textColor = Color.BLACK; 38 | private final Row row; 39 | private WrappingFunction wrappingFunction; 40 | private boolean isHeaderCell = false; 41 | private boolean isColspanCell = false; 42 | 43 | // default padding 44 | private float leftPadding = 5f; 45 | private float rightPadding = 5f; 46 | private float topPadding = 5f; 47 | private float bottomPadding = 5f; 48 | 49 | // default border 50 | private LineStyle leftBorderStyle = new LineStyle(Color.BLACK, 1); 51 | private LineStyle rightBorderStyle = new LineStyle(Color.BLACK, 1); 52 | private LineStyle topBorderStyle = new LineStyle(Color.BLACK, 1); 53 | private LineStyle bottomBorderStyle = new LineStyle(Color.BLACK, 1); 54 | 55 | private Paragraph paragraph = null; 56 | private float lineSpacing = 1; 57 | private boolean textRotated = false; 58 | 59 | private HorizontalAlignment align; 60 | private VerticalAlignment valign; 61 | 62 | float horizontalFreeSpace = 0; 63 | float verticalFreeSpace = 0; 64 | 65 | private final List> contentDrawnListenerList = new ArrayList>(); 66 | 67 | /** 68 | *

69 | * Constructs a cell with the default alignment 70 | * {@link VerticalAlignment#TOP} {@link HorizontalAlignment#LEFT}. 71 | *

72 | * 73 | * @param row 74 | * @param width 75 | * @param text 76 | * @param isCalculated 77 | * @see Cell#Cell(Row, float, String, boolean, HorizontalAlignment, 78 | * VerticalAlignment) 79 | */ 80 | Cell(Row row, float width, String text, boolean isCalculated) { 81 | this(row, width, text, isCalculated, HorizontalAlignment.LEFT, VerticalAlignment.TOP); 82 | } 83 | 84 | /** 85 | *

86 | * Constructs a cell. 87 | *

88 | * 89 | * @param row 90 | * The parent row 91 | * @param width 92 | * absolute width in points or in % of table width (depending on 93 | * the parameter {@code isCalculated}) 94 | * @param text 95 | * The text content of the cell 96 | * @param isCalculated 97 | * If {@code true}, the width is interpreted in % to the table 98 | * width 99 | * @param align 100 | * The {@link HorizontalAlignment} of the cell content 101 | * @param valign 102 | * The {@link VerticalAlignment} of the cell content 103 | * @see Cell#Cell(Row, float, String, boolean) 104 | */ 105 | Cell(Row row, float width, String text, boolean isCalculated, HorizontalAlignment align, 106 | VerticalAlignment valign) { 107 | this.row = row; 108 | if (isCalculated) { 109 | double calculatedWidth = row.getWidth() * (width / 100); 110 | this.width = (float) calculatedWidth; 111 | } else { 112 | this.width = width; 113 | } 114 | 115 | if (getWidth() > row.getWidth()) { 116 | throw new IllegalArgumentException( 117 | "Cell Width=" + getWidth() + " can't be bigger than row width=" + row.getWidth()); 118 | } 119 | //check if we have new default font 120 | if(!FontUtils.getDefaultfonts().isEmpty()){ 121 | font = FontUtils.getDefaultfonts().get("font"); 122 | fontBold = FontUtils.getDefaultfonts().get("fontBold"); 123 | } 124 | this.text = text == null ? "" : text; 125 | this.align = align; 126 | this.valign = valign; 127 | this.wrappingFunction = null; 128 | } 129 | 130 | /** 131 | *

132 | * Retrieves cell's text {@link Color}. Default color is black. 133 | *

134 | * 135 | * @return {@link Color} of the cell's text 136 | */ 137 | public Color getTextColor() { 138 | return textColor; 139 | } 140 | 141 | /** 142 | *

143 | * Sets cell's text {@link Color}. 144 | *

145 | * 146 | * @param textColor 147 | * designated text {@link Color} 148 | */ 149 | public void setTextColor(Color textColor) { 150 | this.textColor = textColor; 151 | } 152 | 153 | /** 154 | *

155 | * Gets fill (background) {@link Color} for the current cell. 156 | *

157 | * 158 | * @return Fill {@link Color} for the cell 159 | */ 160 | public Color getFillColor() { 161 | return fillColor; 162 | } 163 | 164 | /** 165 | *

166 | * Sets fill (background) {@link Color} for the current cell. 167 | *

168 | * 169 | * @param fillColor 170 | * Fill {@link Color} for the cell 171 | */ 172 | public void setFillColor(Color fillColor) { 173 | this.fillColor = fillColor; 174 | } 175 | 176 | /** 177 | *

178 | * Gets cell's width. 179 | *

180 | * 181 | * @return Cell's width 182 | */ 183 | public float getWidth() { 184 | return width; 185 | } 186 | 187 | /** 188 | *

189 | * Gets cell's width without (left,right) padding. 190 | * 191 | * @return Inner cell's width 192 | */ 193 | public float getInnerWidth() { 194 | return getWidth() - getLeftPadding() - getRightPadding() 195 | - (leftBorderStyle == null ? 0 : leftBorderStyle.getWidth()) 196 | - (rightBorderStyle == null ? 0 : rightBorderStyle.getWidth()); 197 | } 198 | 199 | /** 200 | *

201 | * Gets cell's height without (top,bottom) padding. 202 | * 203 | * @return Inner cell's height 204 | */ 205 | public float getInnerHeight() { 206 | return getHeight() - getBottomPadding() - getTopPadding() 207 | - (topBorderStyle == null ? 0 : topBorderStyle.getWidth()) 208 | - (bottomBorderStyle == null ? 0 : bottomBorderStyle.getWidth()); 209 | } 210 | 211 | /** 212 | *

213 | * Retrieves text from current cell 214 | *

215 | * 216 | * @return cell's text 217 | */ 218 | public String getText() { 219 | return text; 220 | } 221 | 222 | /** 223 | *

224 | * Sets cell's text value 225 | *

226 | * 227 | * @param text 228 | * Text value of the cell 229 | */ 230 | public void setText(String text) { 231 | this.text = text; 232 | 233 | // paragraph invalidated 234 | paragraph = null; 235 | } 236 | 237 | /** 238 | *

239 | * Gets appropriate {@link PDFont} for current cell. 240 | *

241 | * 242 | * @return {@link PDFont} for current cell 243 | * @throws IllegalArgumentException 244 | * if font is not set. 245 | */ 246 | public PDFont getFont() { 247 | if (font == null) { 248 | throw new IllegalArgumentException("Font not set."); 249 | } 250 | if (isHeaderCell) { 251 | return fontBold; 252 | } else { 253 | return font; 254 | } 255 | } 256 | 257 | /** 258 | *

259 | * Sets appropriate {@link PDFont} for current cell. 260 | *

261 | * 262 | * @param font 263 | * {@link PDFont} for current cell 264 | */ 265 | public void setFont(PDFont font) { 266 | this.font = font; 267 | 268 | // paragraph invalidated 269 | paragraph = null; 270 | } 271 | 272 | /** 273 | *

274 | * Gets {@link PDFont} size for current cell (in points). 275 | *

276 | * 277 | * @return {@link PDFont} size for current cell (in points). 278 | */ 279 | public float getFontSize() { 280 | return fontSize; 281 | } 282 | 283 | /** 284 | *

285 | * Sets {@link PDFont} size for current cell (in points). 286 | *

287 | * 288 | * @param fontSize 289 | * {@link PDFont} size for current cell (in points). 290 | */ 291 | public void setFontSize(float fontSize) { 292 | this.fontSize = fontSize; 293 | 294 | // paragraph invalidated 295 | paragraph = null; 296 | } 297 | 298 | /** 299 | *

300 | * Retrieves a valid {@link Paragraph} depending of cell's {@link PDFont} 301 | * and value rotation. 302 | *

303 | * 304 | *

305 | * If cell has rotated value then {@link Paragraph} width is depending of 306 | * {@link Cell#getInnerHeight()} otherwise {@link Cell#getInnerWidth()} 307 | *

308 | * 309 | * 310 | * @return Cell's {@link Paragraph} 311 | */ 312 | public Paragraph getParagraph() { 313 | if (paragraph == null) { 314 | // if it is header cell then use font bold 315 | if (isHeaderCell) { 316 | if (isTextRotated()) { 317 | paragraph = new Paragraph(text, fontBold, fontSize, getInnerHeight(), align, textColor, null, 318 | wrappingFunction, lineSpacing); 319 | } else { 320 | paragraph = new Paragraph(text, fontBold, fontSize, getInnerWidth(), align, textColor, null, 321 | wrappingFunction, lineSpacing); 322 | } 323 | } else { 324 | if (isTextRotated()) { 325 | paragraph = new Paragraph(text, font, fontSize, getInnerHeight(), align, textColor, null, 326 | wrappingFunction, lineSpacing); 327 | } else { 328 | paragraph = new Paragraph(text, font, fontSize, getInnerWidth(), align, textColor, null, 329 | wrappingFunction, lineSpacing); 330 | } 331 | } 332 | } 333 | return paragraph; 334 | } 335 | 336 | public float getExtraWidth() { 337 | return this.row.getLastCellExtraWidth() + getWidth(); 338 | } 339 | 340 | /** 341 | *

342 | * Gets the cell's height according to {@link Row}'s height 343 | *

344 | * 345 | * @return {@link Row}'s height 346 | */ 347 | public float getHeight() { 348 | return row.getHeight(); 349 | } 350 | 351 | /** 352 | *

353 | * Gets the height of the single cell, opposed to {@link #getHeight()}, 354 | * which returns the row's height. 355 | *

356 | *

357 | * Depending of rotated/normal cell's value there is two cases for 358 | * calculation: 359 | *

360 | *
    361 | *
  1. Rotated value - cell's height is equal to overall text length in the 362 | * cell with necessery paddings (top,bottom)
  2. 363 | *
  3. Normal value - cell's height is equal to {@link Paragraph}'s height 364 | * with necessery paddings (top,bottom)
  4. 365 | *
366 | * 367 | * @return Cell's height 368 | * @throws IllegalStateException 369 | * if font is not set. 370 | */ 371 | public float getCellHeight() { 372 | if (height != null) { 373 | return height; 374 | } 375 | 376 | if (isTextRotated()) { 377 | try { 378 | // TODO: maybe find more optimal way then this 379 | return getFont().getStringWidth(getText()) / 1000 * getFontSize() + getTopPadding() 380 | + (getTopBorder() == null ? 0 : getTopBorder().getWidth()) + getBottomPadding() 381 | + (getBottomBorder() == null ? 0 : getBottomBorder().getWidth()); 382 | } catch (final IOException e) { 383 | throw new IllegalStateException("Font not set.", e); 384 | } 385 | } else { 386 | return getTextHeight() + getTopPadding() + getBottomPadding() 387 | + (getTopBorder() == null ? 0 : getTopBorder().getWidth()) 388 | + (getBottomBorder() == null ? 0 : getBottomBorder().getWidth()); 389 | } 390 | } 391 | 392 | /** 393 | *

394 | * Sets the height of the single cell. 395 | *

396 | * 397 | * @param height 398 | * Cell's height 399 | */ 400 | public void setHeight(final Float height) { 401 | this.height = height; 402 | } 403 | 404 | /** 405 | *

406 | * Gets {@link Paragraph}'s height 407 | *

408 | * 409 | * @return {@link Paragraph}'s height 410 | */ 411 | public float getTextHeight() { 412 | return getParagraph().getHeight(); 413 | } 414 | 415 | /** 416 | *

417 | * Gets {@link Paragraph}'s width 418 | *

419 | * 420 | * @return {@link Paragraph}'s width 421 | */ 422 | public float getTextWidth() { 423 | return getParagraph().getWidth(); 424 | } 425 | 426 | /** 427 | *

428 | * Gets cell's left padding (in points). 429 | *

430 | * 431 | * @return Cell's left padding (in points). 432 | */ 433 | public float getLeftPadding() { 434 | return leftPadding; 435 | } 436 | 437 | /** 438 | *

439 | * Sets cell's left padding (in points) 440 | *

441 | * 442 | * @param cellLeftPadding 443 | * Cell's left padding (in points). 444 | */ 445 | public void setLeftPadding(float cellLeftPadding) { 446 | this.leftPadding = cellLeftPadding; 447 | 448 | // paragraph invalidated 449 | paragraph = null; 450 | } 451 | 452 | /** 453 | *

454 | * Gets cell's right padding (in points). 455 | *

456 | * 457 | * @return Cell's right padding (in points). 458 | */ 459 | public float getRightPadding() { 460 | return rightPadding; 461 | } 462 | 463 | /** 464 | *

465 | * Sets cell's right padding (in points) 466 | *

467 | * 468 | * @param cellRightPadding 469 | * Cell's right padding (in points). 470 | */ 471 | public void setRightPadding(float cellRightPadding) { 472 | this.rightPadding = cellRightPadding; 473 | 474 | // paragraph invalidated 475 | paragraph = null; 476 | } 477 | 478 | /** 479 | *

480 | * Gets cell's top padding (in points). 481 | *

482 | * 483 | * @return Cell's top padding (in points). 484 | */ 485 | public float getTopPadding() { 486 | return topPadding; 487 | } 488 | 489 | /** 490 | *

491 | * Sets cell's top padding (in points) 492 | *

493 | * 494 | * @param cellTopPadding 495 | * Cell's top padding (in points). 496 | */ 497 | public void setTopPadding(float cellTopPadding) { 498 | this.topPadding = cellTopPadding; 499 | } 500 | 501 | /** 502 | *

503 | * Gets cell's bottom padding (in points). 504 | *

505 | * 506 | * @return Cell's bottom padding (in points). 507 | */ 508 | public float getBottomPadding() { 509 | return bottomPadding; 510 | } 511 | 512 | /** 513 | *

514 | * Sets cell's bottom padding (in points) 515 | *

516 | * 517 | * @param cellBottomPadding 518 | * Cell's bottom padding (in points). 519 | */ 520 | public void setBottomPadding(float cellBottomPadding) { 521 | this.bottomPadding = cellBottomPadding; 522 | } 523 | 524 | /** 525 | *

526 | * Gets free vertical space of cell. 527 | *

528 | * 529 | *

530 | * If cell has rotated value then free vertical space is equal inner cell's 531 | * height ({@link #getInnerHeight()}) subtracted to the longest line of 532 | * rotated {@link Paragraph} otherwise it's just cell's inner height ( 533 | * {@link #getInnerHeight()}) subtracted with width of the normal 534 | * {@link Paragraph}. 535 | *

536 | * 537 | * @return Free vertical space of the cell's. 538 | */ 539 | public float getVerticalFreeSpace() { 540 | if (isTextRotated()) { 541 | // need to calculate max line width so we just iterating through 542 | // lines 543 | for (String line : getParagraph().getLines()) { 544 | } 545 | return getInnerHeight() - getParagraph().getMaxLineWidth(); 546 | } else { 547 | return getInnerHeight() - getTextHeight(); 548 | } 549 | } 550 | 551 | /** 552 | *

553 | * Gets free horizontal space of cell. 554 | *

555 | * 556 | *

557 | * If cell has rotated value then free horizontal space is equal cell's 558 | * inner width ({@link #getInnerWidth()}) subtracted to the 559 | * {@link Paragraph}'s height otherwise it's just cell's 560 | * {@link #getInnerWidth()} subtracted with width of longest line in normal 561 | * {@link Paragraph}. 562 | *

563 | * 564 | * @return Free vertical space of the cell's. 565 | */ 566 | public float getHorizontalFreeSpace() { 567 | if (isTextRotated()) { 568 | return getInnerWidth() - getTextHeight(); 569 | } else { 570 | return getInnerWidth() - getParagraph().getMaxLineWidth(); 571 | } 572 | } 573 | 574 | public HorizontalAlignment getAlign() { 575 | return align; 576 | } 577 | 578 | public VerticalAlignment getValign() { 579 | return valign; 580 | } 581 | 582 | public boolean isHeaderCell() { 583 | return isHeaderCell; 584 | } 585 | 586 | public void setHeaderCell(boolean isHeaderCell) { 587 | this.isHeaderCell = isHeaderCell; 588 | } 589 | 590 | public WrappingFunction getWrappingFunction() { 591 | return getParagraph().getWrappingFunction(); 592 | } 593 | 594 | public void setWrappingFunction(WrappingFunction wrappingFunction) { 595 | this.wrappingFunction = wrappingFunction; 596 | 597 | // paragraph invalidated 598 | paragraph = null; 599 | } 600 | 601 | public LineStyle getLeftBorder() { 602 | return leftBorderStyle; 603 | } 604 | 605 | public LineStyle getRightBorder() { 606 | return rightBorderStyle; 607 | } 608 | 609 | public LineStyle getTopBorder() { 610 | return topBorderStyle; 611 | } 612 | 613 | public LineStyle getBottomBorder() { 614 | return bottomBorderStyle; 615 | } 616 | 617 | public void setLeftBorderStyle(LineStyle leftBorder) { 618 | this.leftBorderStyle = leftBorder; 619 | } 620 | 621 | public void setRightBorderStyle(LineStyle rightBorder) { 622 | this.rightBorderStyle = rightBorder; 623 | } 624 | 625 | public void setTopBorderStyle(LineStyle topBorder) { 626 | this.topBorderStyle = topBorder; 627 | } 628 | 629 | public void setBottomBorderStyle(LineStyle bottomBorder) { 630 | this.bottomBorderStyle = bottomBorder; 631 | } 632 | 633 | /** 634 | *

635 | * Easy setting for cell border style. 636 | * 637 | * @param border 638 | * It is {@link LineStyle} for all borders 639 | * @see LineStyle Rendering line attributes 640 | */ 641 | public void setBorderStyle(LineStyle border) { 642 | this.leftBorderStyle = border; 643 | this.rightBorderStyle = border; 644 | this.topBorderStyle = border; 645 | this.bottomBorderStyle = border; 646 | } 647 | 648 | public boolean isTextRotated() { 649 | return textRotated; 650 | } 651 | 652 | public void setTextRotated(boolean textRotated) { 653 | this.textRotated = textRotated; 654 | } 655 | 656 | public PDFont getFontBold() { 657 | return fontBold; 658 | } 659 | 660 | /** 661 | *

662 | * Sets the {@linkplain PDFont font} used for bold text, for example in 663 | * {@linkplain #isHeaderCell() header cells}. 664 | *

665 | * 666 | * @param fontBold 667 | * The {@linkplain PDFont font} to use for bold text 668 | */ 669 | public void setFontBold(final PDFont fontBold) { 670 | this.fontBold = fontBold; 671 | } 672 | 673 | public boolean isColspanCell() { 674 | return isColspanCell; 675 | } 676 | 677 | public void setColspanCell(boolean isColspanCell) { 678 | this.isColspanCell = isColspanCell; 679 | } 680 | 681 | public void setAlign(HorizontalAlignment align) { 682 | this.align = align; 683 | } 684 | 685 | public void setValign(VerticalAlignment valign) { 686 | this.valign = valign; 687 | } 688 | 689 | /** 690 | *

691 | * Copies the style of an existing cell to this cell 692 | *

693 | * 694 | * @param sourceCell Source {@link Cell} from which cell style will be copied. 695 | */ 696 | public void copyCellStyle(Cell sourceCell) { 697 | Boolean leftBorder = this.leftBorderStyle == null; 698 | setBorderStyle(sourceCell.getTopBorder()); 699 | if (leftBorder) { 700 | this.leftBorderStyle = null;// if left border wasn't set, don't set 701 | // it now 702 | } 703 | this.font = sourceCell.getFont();// otherwise paragraph gets invalidated 704 | this.fontBold = sourceCell.getFontBold(); 705 | this.fontSize = sourceCell.getFontSize(); 706 | setFillColor(sourceCell.getFillColor()); 707 | setTextColor(sourceCell.getTextColor()); 708 | setAlign(sourceCell.getAlign()); 709 | setValign(sourceCell.getValign()); 710 | } 711 | 712 | /** 713 | *

714 | * Compares the style of a cell with another cell 715 | *

716 | * 717 | * @param sourceCell Source {@link Cell} which will be used for style comparation 718 | * @return boolean if source cell has the same style 719 | */ 720 | public Boolean hasSameStyle(Cell sourceCell) { 721 | if (!sourceCell.getTopBorder().equals(getTopBorder())) { 722 | return false; 723 | } 724 | if (!sourceCell.getFont().equals(getFont())) { 725 | return false; 726 | } 727 | if (!sourceCell.getFontBold().equals(getFontBold())) { 728 | return false; 729 | } 730 | if (!sourceCell.getFillColor().equals(getFillColor())) { 731 | return false; 732 | } 733 | if (!sourceCell.getTextColor().equals(getTextColor())) { 734 | return false; 735 | } 736 | if (!sourceCell.getAlign().equals(getAlign())) { 737 | return false; 738 | } 739 | if (!sourceCell.getValign().equals(getValign())) { 740 | return false; 741 | } 742 | return true; 743 | } 744 | 745 | public void setWidth(float width) { 746 | this.width = width; 747 | } 748 | 749 | public float getLineSpacing() { 750 | return lineSpacing; 751 | } 752 | 753 | public void setLineSpacing(float lineSpacing) { 754 | this.lineSpacing = lineSpacing; 755 | } 756 | 757 | public void addContentDrawnListener(CellContentDrawnListener listener) { 758 | contentDrawnListenerList.add(listener); 759 | } 760 | 761 | public List> getCellContentDrawnListeners() { 762 | return contentDrawnListenerList; 763 | } 764 | 765 | public void notifyContentDrawnListeners(PDDocument document, PDPage page, PDRectangle rectangle) { 766 | for(CellContentDrawnListener listener : getCellContentDrawnListeners()) { 767 | listener.onContentDrawn(this, document, page, rectangle); 768 | } 769 | } 770 | 771 | public URL getUrl() { 772 | return url; 773 | } 774 | 775 | public void setUrl(URL url) { 776 | this.url = url; 777 | } 778 | 779 | 780 | } 781 | --------------------------------------------------------------------------------