adding table, boxable, better font descriptor with TTF fonts
This commit is contained in:
parent
d434a9e4ee
commit
4ad07247b2
109 changed files with 8698 additions and 1076 deletions
|
@ -228,7 +228,7 @@ abstract class BaseBuilderSpec extends Specification {
|
|||
builder.create {
|
||||
document {
|
||||
paragraph {
|
||||
barcode(height: 150.px, width: 500.px, value: '12345678')
|
||||
barcode(height: 150.px, width: 500.px, value: '12345678', type: 'CODE39')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
module org.xbib.graphics.layout.pdfbox {
|
||||
exports org.xbib.graphics.pdfbox.layout.boxable;
|
||||
exports org.xbib.graphics.pdfbox.layout.elements;
|
||||
exports org.xbib.graphics.pdfbox.layout.elements.render;
|
||||
exports org.xbib.graphics.pdfbox.layout.font;
|
||||
exports org.xbib.graphics.pdfbox.layout.shape;
|
||||
exports org.xbib.graphics.pdfbox.layout.table;
|
||||
exports org.xbib.graphics.pdfbox.layout.table.render;
|
||||
exports org.xbib.graphics.pdfbox.layout.text;
|
||||
exports org.xbib.graphics.pdfbox.layout.text.annotations;
|
||||
exports org.xbib.graphics.pdfbox.layout.util;
|
||||
requires transitive org.xbib.graphics.barcode;
|
||||
requires transitive org.xbib.graphics.pdfbox;
|
||||
requires transitive java.desktop;
|
||||
requires java.logging;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BaseTable extends Table<PDPage> {
|
||||
|
||||
public BaseTable(float yStart, float yStartNewPage, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException {
|
||||
super(yStart, yStartNewPage, 0, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox()));
|
||||
}
|
||||
|
||||
public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent) throws IOException {
|
||||
super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, new DefaultPageProvider(document, currentPage.getMediaBox()));
|
||||
}
|
||||
|
||||
public BaseTable(float yStart, float yStartNewPage, float pageTopMargin, float bottomMargin, float width, float margin, PDDocument document, PDPage currentPage, boolean drawLines, boolean drawContent, final PageProvider<PDPage> pageProvider) throws IOException {
|
||||
super(yStart, yStartNewPage, pageTopMargin, bottomMargin, width, margin, document, currentPage, drawLines, drawContent, pageProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFonts() {
|
||||
// Do nothing as we don't have any fonts to load
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,707 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Cell<T extends PDPage> {
|
||||
|
||||
private float width;
|
||||
private Float height;
|
||||
private String text;
|
||||
|
||||
private PDFont font = PDType1Font.HELVETICA;
|
||||
private PDFont fontBold = PDType1Font.HELVETICA_BOLD;
|
||||
|
||||
private float fontSize = 8;
|
||||
private Color fillColor;
|
||||
private Color textColor = Color.BLACK;
|
||||
private final Row<T> row;
|
||||
private WrappingFunction wrappingFunction;
|
||||
private boolean isHeaderCell = false;
|
||||
private boolean isColspanCell = false;
|
||||
|
||||
// default padding
|
||||
private float leftPadding = 5f;
|
||||
private float rightPadding = 5f;
|
||||
private float topPadding = 5f;
|
||||
private float bottomPadding = 5f;
|
||||
|
||||
// default border
|
||||
private LineStyle leftBorderStyle = new LineStyle(Color.BLACK, 1);
|
||||
private LineStyle rightBorderStyle = new LineStyle(Color.BLACK, 1);
|
||||
private LineStyle topBorderStyle = new LineStyle(Color.BLACK, 1);
|
||||
private LineStyle bottomBorderStyle = new LineStyle(Color.BLACK, 1);
|
||||
|
||||
private Paragraph paragraph = null;
|
||||
private float lineSpacing = 1;
|
||||
private boolean textRotated = false;
|
||||
|
||||
private HorizontalAlignment align;
|
||||
private VerticalAlignment valign;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructs a cell with the default alignment
|
||||
* {@link VerticalAlignment#TOP} {@link HorizontalAlignment#LEFT}.
|
||||
* </p>
|
||||
*
|
||||
* @param row
|
||||
* @param width
|
||||
* @param text
|
||||
* @param isCalculated
|
||||
* @see Cell#Cell(Row, float, String, boolean, HorizontalAlignment,
|
||||
* VerticalAlignment)
|
||||
*/
|
||||
Cell(Row<T> row, float width, String text, boolean isCalculated) {
|
||||
this(row, width, text, isCalculated, HorizontalAlignment.LEFT, VerticalAlignment.TOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a cell.
|
||||
*
|
||||
* @param row The parent row
|
||||
* @param width absolute width in points or in % of table width (depending on
|
||||
* the parameter {@code isCalculated})
|
||||
* @param text The text content of the cell
|
||||
* @param isCalculated If {@code true}, the width is interpreted in % to the table
|
||||
* width
|
||||
* @param align The {@link HorizontalAlignment} of the cell content
|
||||
* @param valign The {@link VerticalAlignment} of the cell content
|
||||
* @see Cell#Cell(Row, float, String, boolean)
|
||||
*/
|
||||
Cell(Row<T> row, float width, String text, boolean isCalculated, HorizontalAlignment align,
|
||||
VerticalAlignment valign) {
|
||||
this.row = row;
|
||||
if (isCalculated) {
|
||||
double calclulatedWidth = ((row.getWidth() * width) / 100);
|
||||
this.width = (float) calclulatedWidth;
|
||||
} else {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
if (getWidth() > row.getWidth()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cell Width=" + getWidth() + " can't be bigger than row width=" + row.getWidth());
|
||||
}
|
||||
//check if we have new default font
|
||||
if (!FontUtils.getDefaultfonts().isEmpty()) {
|
||||
font = FontUtils.getDefaultfonts().get("font");
|
||||
fontBold = FontUtils.getDefaultfonts().get("fontBold");
|
||||
}
|
||||
this.text = text == null ? "" : text;
|
||||
this.align = align;
|
||||
this.valign = valign;
|
||||
this.wrappingFunction = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves cell's text {@link Color}. Default color is black.
|
||||
*
|
||||
* @return {@link Color} of the cell's text
|
||||
*/
|
||||
public Color getTextColor() {
|
||||
return textColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's text {@link Color}.
|
||||
* </p>
|
||||
*
|
||||
* @param textColor designated text {@link Color}
|
||||
*/
|
||||
public void setTextColor(Color textColor) {
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets fill (background) {@link Color} for the current cell.
|
||||
* </p>
|
||||
*
|
||||
* @return Fill {@link Color} for the cell
|
||||
*/
|
||||
public Color getFillColor() {
|
||||
return fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets fill (background) {@link Color} for the current cell.
|
||||
* </p>
|
||||
*
|
||||
* @param fillColor Fill {@link Color} for the cell
|
||||
*/
|
||||
public void setFillColor(Color fillColor) {
|
||||
this.fillColor = fillColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's width.
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's width
|
||||
*/
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's width without (left,right) padding.
|
||||
*
|
||||
* @return Inner cell's width
|
||||
*/
|
||||
public float getInnerWidth() {
|
||||
return getWidth() - getLeftPadding() - getRightPadding()
|
||||
- (leftBorderStyle == null ? 0 : leftBorderStyle.getWidth())
|
||||
- (rightBorderStyle == null ? 0 : rightBorderStyle.getWidth());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's height without (top,bottom) padding.
|
||||
*
|
||||
* @return Inner cell's height
|
||||
*/
|
||||
public float getInnerHeight() {
|
||||
return getHeight() - getBottomPadding() - getTopPadding()
|
||||
- (topBorderStyle == null ? 0 : topBorderStyle.getWidth())
|
||||
- (bottomBorderStyle == null ? 0 : bottomBorderStyle.getWidth());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Retrieves text from current cell
|
||||
* </p>
|
||||
*
|
||||
* @return cell's text
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's text value
|
||||
* </p>
|
||||
*
|
||||
* @param text Text value of the cell
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets appropriate {@link PDFont} for current cell.
|
||||
* </p>
|
||||
*
|
||||
* @return {@link PDFont} for current cell
|
||||
* @throws IllegalArgumentException if <code>font</code> is not set.
|
||||
*/
|
||||
public PDFont getFont() {
|
||||
if (font == null) {
|
||||
throw new IllegalArgumentException("Font not set.");
|
||||
}
|
||||
if (isHeaderCell) {
|
||||
return fontBold;
|
||||
} else {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets appropriate {@link PDFont} for current cell.
|
||||
* </p>
|
||||
*
|
||||
* @param font {@link PDFont} for current cell
|
||||
*/
|
||||
public void setFont(PDFont font) {
|
||||
this.font = font;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets {@link PDFont} size for current cell (in points).
|
||||
* </p>
|
||||
*
|
||||
* @return {@link PDFont} size for current cell (in points).
|
||||
*/
|
||||
public float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets {@link PDFont} size for current cell (in points).
|
||||
* </p>
|
||||
*
|
||||
* @param fontSize {@link PDFont} size for current cell (in points).
|
||||
*/
|
||||
public void setFontSize(float fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Retrieves a valid {@link Paragraph} depending of cell's {@link PDFont}
|
||||
* and value rotation.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If cell has rotated value then {@link Paragraph} width is depending of
|
||||
* {@link Cell#getInnerHeight()} otherwise {@link Cell#getInnerWidth()}
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's {@link Paragraph}
|
||||
*/
|
||||
public Paragraph getParagraph() {
|
||||
if (paragraph == null) {
|
||||
// if it is header cell then use font bold
|
||||
if (isHeaderCell) {
|
||||
if (isTextRotated()) {
|
||||
paragraph = new Paragraph(text, fontBold, fontSize, getInnerHeight(), align, textColor, null,
|
||||
wrappingFunction, lineSpacing);
|
||||
} else {
|
||||
paragraph = new Paragraph(text, fontBold, fontSize, getInnerWidth(), align, textColor, null,
|
||||
wrappingFunction, lineSpacing);
|
||||
}
|
||||
} else {
|
||||
if (isTextRotated()) {
|
||||
paragraph = new Paragraph(text, font, fontSize, getInnerHeight(), align, textColor, null,
|
||||
wrappingFunction, lineSpacing);
|
||||
} else {
|
||||
paragraph = new Paragraph(text, font, fontSize, getInnerWidth(), align, textColor, null,
|
||||
wrappingFunction, lineSpacing);
|
||||
}
|
||||
}
|
||||
}
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
public float getExtraWidth() {
|
||||
return this.row.getLastCellExtraWidth() + getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the cell's height according to {@link Row}'s height
|
||||
* </p>
|
||||
*
|
||||
* @return {@link Row}'s height
|
||||
*/
|
||||
public float getHeight() {
|
||||
return row.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets the height of the single cell, opposed to {@link #getHeight()},
|
||||
* which returns the row's height.
|
||||
* </p>
|
||||
* <p>
|
||||
* Depending of rotated/normal cell's value there is two cases for
|
||||
* calculation:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>Rotated value - cell's height is equal to overall text length in the
|
||||
* cell with necessery paddings (top,bottom)</li>
|
||||
* <li>Normal value - cell's height is equal to {@link Paragraph}'s height
|
||||
* with necessery paddings (top,bottom)</li>
|
||||
* </ol>
|
||||
*
|
||||
* @return Cell's height
|
||||
* @throws IllegalStateException if <code>font</code> is not set.
|
||||
*/
|
||||
public float getCellHeight() {
|
||||
if (height != null) {
|
||||
return height;
|
||||
}
|
||||
|
||||
if (isTextRotated()) {
|
||||
try {
|
||||
// TODO: maybe find more optimal way then this
|
||||
return getFont().getStringWidth(getText()) / 1000 * getFontSize() + getTopPadding()
|
||||
+ (getTopBorder() == null ? 0 : getTopBorder().getWidth()) + getBottomPadding()
|
||||
+ (getBottomBorder() == null ? 0 : getBottomBorder().getWidth());
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException("Font not set.", e);
|
||||
}
|
||||
} else {
|
||||
return getTextHeight() + getTopPadding() + getBottomPadding()
|
||||
+ (getTopBorder() == null ? 0 : getTopBorder().getWidth())
|
||||
+ (getBottomBorder() == null ? 0 : getBottomBorder().getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the height of the single cell.
|
||||
* </p>
|
||||
*
|
||||
* @param height Cell's height
|
||||
*/
|
||||
public void setHeight(final Float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets {@link Paragraph}'s height
|
||||
* </p>
|
||||
*
|
||||
* @return {@link Paragraph}'s height
|
||||
*/
|
||||
public float getTextHeight() {
|
||||
return getParagraph().getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets {@link Paragraph}'s width
|
||||
* </p>
|
||||
*
|
||||
* @return {@link Paragraph}'s width
|
||||
*/
|
||||
public float getTextWidth() {
|
||||
return getParagraph().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's left padding (in points).
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's left padding (in points).
|
||||
*/
|
||||
public float getLeftPadding() {
|
||||
return leftPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's left padding (in points)
|
||||
* </p>
|
||||
*
|
||||
* @param cellLeftPadding Cell's left padding (in points).
|
||||
*/
|
||||
public void setLeftPadding(float cellLeftPadding) {
|
||||
this.leftPadding = cellLeftPadding;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's right padding (in points).
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's right padding (in points).
|
||||
*/
|
||||
public float getRightPadding() {
|
||||
return rightPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's right padding (in points)
|
||||
* </p>
|
||||
*
|
||||
* @param cellRightPadding Cell's right padding (in points).
|
||||
*/
|
||||
public void setRightPadding(float cellRightPadding) {
|
||||
this.rightPadding = cellRightPadding;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's top padding (in points).
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's top padding (in points).
|
||||
*/
|
||||
public float getTopPadding() {
|
||||
return topPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's top padding (in points)
|
||||
* </p>
|
||||
*
|
||||
* @param cellTopPadding Cell's top padding (in points).
|
||||
*/
|
||||
public void setTopPadding(float cellTopPadding) {
|
||||
this.topPadding = cellTopPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets cell's bottom padding (in points).
|
||||
* </p>
|
||||
*
|
||||
* @return Cell's bottom padding (in points).
|
||||
*/
|
||||
public float getBottomPadding() {
|
||||
return bottomPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets cell's bottom padding (in points)
|
||||
* </p>
|
||||
*
|
||||
* @param cellBottomPadding Cell's bottom padding (in points).
|
||||
*/
|
||||
public void setBottomPadding(float cellBottomPadding) {
|
||||
this.bottomPadding = cellBottomPadding;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets free vertical space of cell.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If cell has rotated value then free vertical space is equal inner cell's
|
||||
* height ({@link #getInnerHeight()}) subtracted to the longest line of
|
||||
* rotated {@link Paragraph} otherwise it's just cell's inner height (
|
||||
* {@link #getInnerHeight()}) subtracted with width of the normal
|
||||
* {@link Paragraph}.
|
||||
* </p>
|
||||
*
|
||||
* @return Free vertical space of the cell's.
|
||||
*/
|
||||
public float getVerticalFreeSpace() {
|
||||
if (isTextRotated()) {
|
||||
// need to calculate max line width so we just iterating through
|
||||
// lines
|
||||
for (String line : getParagraph().getLines()) {
|
||||
}
|
||||
return getInnerHeight() - getParagraph().getMaxLineWidth();
|
||||
} else {
|
||||
return getInnerHeight() - getTextHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets free horizontal space of cell.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* If cell has rotated value then free horizontal space is equal cell's
|
||||
* inner width ({@link #getInnerWidth()}) subtracted to the
|
||||
* {@link Paragraph}'s height otherwise it's just cell's
|
||||
* {@link #getInnerWidth()} subtracted with width of longest line in normal
|
||||
* {@link Paragraph}.
|
||||
* </p>
|
||||
*
|
||||
* @return Free vertical space of the cell's.
|
||||
*/
|
||||
public float getHorizontalFreeSpace() {
|
||||
if (isTextRotated()) {
|
||||
return getInnerWidth() - getTextHeight();
|
||||
} else {
|
||||
return getInnerWidth() - getParagraph().getMaxLineWidth();
|
||||
}
|
||||
}
|
||||
|
||||
public HorizontalAlignment getAlign() {
|
||||
return align;
|
||||
}
|
||||
|
||||
public VerticalAlignment getValign() {
|
||||
return valign;
|
||||
}
|
||||
|
||||
public boolean isHeaderCell() {
|
||||
return isHeaderCell;
|
||||
}
|
||||
|
||||
public void setHeaderCell(boolean isHeaderCell) {
|
||||
this.isHeaderCell = isHeaderCell;
|
||||
}
|
||||
|
||||
public WrappingFunction getWrappingFunction() {
|
||||
return getParagraph().getWrappingFunction();
|
||||
}
|
||||
|
||||
public void setWrappingFunction(WrappingFunction wrappingFunction) {
|
||||
this.wrappingFunction = wrappingFunction;
|
||||
|
||||
// paragraph invalidated
|
||||
paragraph = null;
|
||||
}
|
||||
|
||||
public LineStyle getLeftBorder() {
|
||||
return leftBorderStyle;
|
||||
}
|
||||
|
||||
public LineStyle getRightBorder() {
|
||||
return rightBorderStyle;
|
||||
}
|
||||
|
||||
public LineStyle getTopBorder() {
|
||||
return topBorderStyle;
|
||||
}
|
||||
|
||||
public LineStyle getBottomBorder() {
|
||||
return bottomBorderStyle;
|
||||
}
|
||||
|
||||
public void setLeftBorderStyle(LineStyle leftBorder) {
|
||||
this.leftBorderStyle = leftBorder;
|
||||
}
|
||||
|
||||
public void setRightBorderStyle(LineStyle rightBorder) {
|
||||
this.rightBorderStyle = rightBorder;
|
||||
}
|
||||
|
||||
public void setTopBorderStyle(LineStyle topBorder) {
|
||||
this.topBorderStyle = topBorder;
|
||||
}
|
||||
|
||||
public void setBottomBorderStyle(LineStyle bottomBorder) {
|
||||
this.bottomBorderStyle = bottomBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Easy setting for cell border style.
|
||||
*
|
||||
* @param border It is {@link LineStyle} for all borders
|
||||
* @see LineStyle Rendering line attributes
|
||||
*/
|
||||
public void setBorderStyle(LineStyle border) {
|
||||
this.leftBorderStyle = border;
|
||||
this.rightBorderStyle = border;
|
||||
this.topBorderStyle = border;
|
||||
this.bottomBorderStyle = border;
|
||||
}
|
||||
|
||||
public boolean isTextRotated() {
|
||||
return textRotated;
|
||||
}
|
||||
|
||||
public void setTextRotated(boolean textRotated) {
|
||||
this.textRotated = textRotated;
|
||||
}
|
||||
|
||||
public PDFont getFontBold() {
|
||||
return fontBold;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the {@linkplain PDFont font} used for bold text, for example in
|
||||
* {@linkplain #isHeaderCell() header cells}.
|
||||
* </p>
|
||||
*
|
||||
* @param fontBold The {@linkplain PDFont font} to use for bold text
|
||||
*/
|
||||
public void setFontBold(final PDFont fontBold) {
|
||||
this.fontBold = fontBold;
|
||||
}
|
||||
|
||||
public boolean isColspanCell() {
|
||||
return isColspanCell;
|
||||
}
|
||||
|
||||
public void setColspanCell(boolean isColspanCell) {
|
||||
this.isColspanCell = isColspanCell;
|
||||
}
|
||||
|
||||
public void setAlign(HorizontalAlignment align) {
|
||||
this.align = align;
|
||||
}
|
||||
|
||||
public void setValign(VerticalAlignment valign) {
|
||||
this.valign = valign;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Copies the style of an existing cell to this cell
|
||||
* </p>
|
||||
*
|
||||
* @param sourceCell Source {@link Cell} from which cell style will be copied.
|
||||
*/
|
||||
public void copyCellStyle(Cell<?> sourceCell) {
|
||||
boolean leftBorder = this.leftBorderStyle == null;
|
||||
setBorderStyle(sourceCell.getTopBorder());
|
||||
if (leftBorder) {
|
||||
this.leftBorderStyle = null;// if left border wasn't set, don't set
|
||||
// it now
|
||||
}
|
||||
this.font = sourceCell.getFont();// otherwise paragraph gets invalidated
|
||||
this.fontBold = sourceCell.getFontBold();
|
||||
this.fontSize = sourceCell.getFontSize();
|
||||
setFillColor(sourceCell.getFillColor());
|
||||
setTextColor(sourceCell.getTextColor());
|
||||
setAlign(sourceCell.getAlign());
|
||||
setValign(sourceCell.getValign());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Compares the style of a cell with another cell
|
||||
* </p>
|
||||
*
|
||||
* @param sourceCell Source {@link Cell} which will be used for style comparation
|
||||
* @return boolean if source cell has the same style
|
||||
*/
|
||||
public Boolean hasSameStyle(Cell<?> sourceCell) {
|
||||
if (!sourceCell.getTopBorder().equals(getTopBorder())) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceCell.getFont().equals(getFont())) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceCell.getFontBold().equals(getFontBold())) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceCell.getFillColor().equals(getFillColor())) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceCell.getTextColor().equals(getTextColor())) {
|
||||
return false;
|
||||
}
|
||||
if (!sourceCell.getAlign().equals(getAlign())) {
|
||||
return false;
|
||||
}
|
||||
return sourceCell.getValign().equals(getValign());
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
public void setLineSpacing(float lineSpacing) {
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
|
||||
public class DefaultPageProvider implements PageProvider<PDPage> {
|
||||
|
||||
private final PDDocument document;
|
||||
|
||||
private final PDRectangle size;
|
||||
|
||||
private int currentPageIndex = -1;
|
||||
|
||||
public DefaultPageProvider(final PDDocument document, final PDRectangle size) {
|
||||
this.document = document;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDDocument getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDPage createPage() {
|
||||
currentPageIndex = document.getNumberOfPages();
|
||||
return getCurrentPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDPage nextPage() {
|
||||
if (currentPageIndex == -1) {
|
||||
currentPageIndex = document.getNumberOfPages();
|
||||
} else {
|
||||
currentPageIndex++;
|
||||
}
|
||||
|
||||
return getCurrentPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDPage previousPage() {
|
||||
currentPageIndex--;
|
||||
if (currentPageIndex < 0) {
|
||||
currentPageIndex = 0;
|
||||
}
|
||||
|
||||
return getCurrentPage();
|
||||
}
|
||||
|
||||
private PDPage getCurrentPage() {
|
||||
if (currentPageIndex >= document.getNumberOfPages()) {
|
||||
final PDPage newPage = new PDPage(size);
|
||||
document.addPage(newPage);
|
||||
return newPage;
|
||||
}
|
||||
|
||||
return document.getPage(currentPageIndex);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
class FontMetrics {
|
||||
|
||||
private final float ascent;
|
||||
|
||||
private final float descent;
|
||||
|
||||
private final float height;
|
||||
|
||||
public FontMetrics(final float height, final float ascent, final float descent) {
|
||||
this.height = height;
|
||||
this.ascent = ascent;
|
||||
this.descent = descent;
|
||||
}
|
||||
|
||||
public float getAscent() {
|
||||
return ascent;
|
||||
}
|
||||
|
||||
public float getDescent() {
|
||||
return descent;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility methods for fonts
|
||||
*/
|
||||
public final class FontUtils {
|
||||
|
||||
/**
|
||||
* {@link HashMap} for caching {@link FontMetrics} for designated
|
||||
* {@link PDFont} because {@link FontUtils#getHeight(PDFont, float)} is
|
||||
* expensive to calculate and the results are only approximate.
|
||||
*/
|
||||
private static final Map<String, FontMetrics> fontMetrics = new HashMap<>();
|
||||
|
||||
private static final Map<String, PDFont> defaultFonts = new HashMap<>();
|
||||
|
||||
private FontUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the {@link PDType0Font} to be embedded in the specified
|
||||
* {@link PDDocument}.
|
||||
*
|
||||
* @param document {@link PDDocument} where fonts will be loaded
|
||||
* @param fontPath font path which will be loaded
|
||||
* @return The read {@link PDType0Font}
|
||||
*/
|
||||
public static PDType0Font loadFont(PDDocument document, String fontPath) {
|
||||
try {
|
||||
return PDType0Font.load(document, FontUtils.class.getClassLoader().getResourceAsStream(fontPath));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieving {@link String} width depending on current font size. The width
|
||||
* of the string in 1/1000 units of text space.
|
||||
*
|
||||
* @param font The font of text whose width will be retrieved
|
||||
* @param text The text whose width will be retrieved
|
||||
* @param fontSize The font size of text whose width will be retrieved
|
||||
* @return text width
|
||||
*/
|
||||
public static float getStringWidth(final PDFont font, final String text, final float fontSize) {
|
||||
try {
|
||||
return font.getStringWidth(text) / 1000 * fontSize;
|
||||
} catch (final IOException e) {
|
||||
throw new UncheckedIOException("Unable to determine text width", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calculate the font ascent distance.
|
||||
* </p>
|
||||
*
|
||||
* @param font The font from which calculation will be applied
|
||||
* @param fontSize The font size from which calculation will be applied
|
||||
* @return Positive font ascent distance
|
||||
*/
|
||||
public static float getAscent(final PDFont font, final float fontSize) {
|
||||
final String fontName = font.getName();
|
||||
if (!fontMetrics.containsKey(fontName)) {
|
||||
createFontMetrics(font);
|
||||
}
|
||||
|
||||
return fontMetrics.get(fontName).getAscent() * fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calculate the font descent distance.
|
||||
* </p>
|
||||
*
|
||||
* @param font The font from which calculation will be applied
|
||||
* @param fontSize The font size from which calculation will be applied
|
||||
* @return Negative font descent distance
|
||||
*/
|
||||
public static float getDescent(final PDFont font, final float fontSize) {
|
||||
final String fontName = font.getName();
|
||||
if (!fontMetrics.containsKey(fontName)) {
|
||||
createFontMetrics(font);
|
||||
}
|
||||
|
||||
return fontMetrics.get(fontName).getDescent() * fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calculate the font height.
|
||||
* </p>
|
||||
*
|
||||
* @param font {@link PDFont} from which the height will be calculated.
|
||||
* @param fontSize font size for current {@link PDFont}.
|
||||
* @return {@link PDFont}'s height
|
||||
*/
|
||||
public static float getHeight(final PDFont font, final float fontSize) {
|
||||
final String fontName = font.getName();
|
||||
if (!fontMetrics.containsKey(fontName)) {
|
||||
createFontMetrics(font);
|
||||
}
|
||||
|
||||
return fontMetrics.get(fontName).getHeight() * fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Create basic {@link FontMetrics} for current font.
|
||||
* <p>
|
||||
*
|
||||
* @param font The font from which calculation will be applied <<<<<<< HEAD
|
||||
*/
|
||||
private static void createFontMetrics(final PDFont font) {
|
||||
final float base = font.getFontDescriptor().getXHeight() / 1000;
|
||||
final float ascent = font.getFontDescriptor().getAscent() / 1000 - base;
|
||||
final float descent = font.getFontDescriptor().getDescent() / 1000;
|
||||
fontMetrics.put(font.getName(), new FontMetrics(base + ascent - descent, ascent, descent));
|
||||
}
|
||||
|
||||
public static Map<String, PDFont> getDefaultfonts() {
|
||||
return defaultFonts;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Data container for HTML ordered list elements.
|
||||
* </p>
|
||||
*
|
||||
* @author hstimac
|
||||
*/
|
||||
public class HTMLListNode {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Element's current ordering number (e.g third element in the current list)
|
||||
* </p>
|
||||
*/
|
||||
private int orderingNumber;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Element's whole ordering number value (e.g 1.1.2.1)
|
||||
* </p>
|
||||
*/
|
||||
private String value;
|
||||
|
||||
public int getOrderingNumber() {
|
||||
return orderingNumber;
|
||||
}
|
||||
|
||||
public void setOrderingNumber(int orderingNumber) {
|
||||
this.orderingNumber = orderingNumber;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public HTMLListNode(int orderingNumber, String value) {
|
||||
this.orderingNumber = orderingNumber;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
public enum HorizontalAlignment {
|
||||
LEFT, CENTER, RIGHT;
|
||||
|
||||
public static HorizontalAlignment get(final String key) {
|
||||
switch (key == null ? "left" : key.toLowerCase().trim()) {
|
||||
case "left":
|
||||
return LEFT;
|
||||
case "center":
|
||||
return CENTER;
|
||||
case "right":
|
||||
return RIGHT;
|
||||
default:
|
||||
return LEFT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Image {
|
||||
|
||||
private final BufferedImage image;
|
||||
|
||||
private float width;
|
||||
|
||||
private float height;
|
||||
|
||||
private PDImageXObject imageXObject = null;
|
||||
|
||||
private final float[] dpi = {72, 72};
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Constructor for default images
|
||||
* </p>
|
||||
*
|
||||
* @param image {@link BufferedImage}
|
||||
*/
|
||||
public Image(final BufferedImage image) {
|
||||
this.image = image;
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
}
|
||||
|
||||
public Image(final BufferedImage image, float dpi) {
|
||||
this(image, dpi, dpi);
|
||||
}
|
||||
|
||||
public Image(final BufferedImage image, float dpiX, float dpiY) {
|
||||
this.image = image;
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
this.dpi[0] = dpiX;
|
||||
this.dpi[1] = dpiY;
|
||||
scaleImageFromPixelToPoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Drawing simple {@link Image} in {@link PDPageContentStream}.
|
||||
* </p>
|
||||
*
|
||||
* @param doc {@link PDDocument} where drawing will be applied
|
||||
* @param stream {@link PDPageContentStream} where drawing will be applied
|
||||
* @param x X coordinate for image drawing
|
||||
* @param y Y coordinate for image drawing
|
||||
* @throws IOException if loading image fails
|
||||
*/
|
||||
public void draw(final PDDocument doc, final PageContentStreamOptimized stream, float x, float y) throws IOException {
|
||||
if (imageXObject == null) {
|
||||
imageXObject = LosslessFactory.createFromImage(doc, image);
|
||||
}
|
||||
stream.drawImage(imageXObject, x, y - height, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Method which scale {@link Image} with designated width
|
||||
* </p>
|
||||
*
|
||||
* @param width Maximal width where {@link Image} needs to be scaled
|
||||
* @return Scaled {@link Image}
|
||||
*/
|
||||
public Image scaleByWidth(float width) {
|
||||
float factorWidth = width / this.width;
|
||||
return scale(width, this.height * factorWidth);
|
||||
}
|
||||
|
||||
private void scaleImageFromPixelToPoints() {
|
||||
float dpiX = dpi[0];
|
||||
float dpiY = dpi[1];
|
||||
scale(getImageWidthInPoints(dpiX), getImageHeightInPoints(dpiY));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Method which scale {@link Image} with designated height
|
||||
*
|
||||
* @param height Maximal height where {@link Image} needs to be scaled
|
||||
* @return Scaled {@link Image}
|
||||
*/
|
||||
public Image scaleByHeight(float height) {
|
||||
float factorHeight = height / this.height;
|
||||
return scale(this.width * factorHeight, height);
|
||||
}
|
||||
|
||||
public float getImageWidthInPoints(float dpiX) {
|
||||
return this.width * 72f / dpiX;
|
||||
}
|
||||
|
||||
public float getImageHeightInPoints(float dpiY) {
|
||||
return this.height * 72f / dpiY;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Method which scale {@link Image} with designated width und height
|
||||
*
|
||||
* @param boundWidth Maximal width where {@link Image} needs to be scaled
|
||||
* @param boundHeight Maximal height where {@link Image} needs to be scaled
|
||||
* @return scaled {@link Image}
|
||||
*/
|
||||
public Image scale(float boundWidth, float boundHeight) {
|
||||
float[] imageDimension = ImageUtils.getScaledDimension(this.width, this.height, boundWidth, boundHeight);
|
||||
this.width = imageDimension[0];
|
||||
this.height = imageDimension[1];
|
||||
return this;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
|
||||
public class ImageCell<T extends PDPage> extends Cell<T> {
|
||||
|
||||
private Image img;
|
||||
|
||||
private final HorizontalAlignment align;
|
||||
|
||||
private final VerticalAlignment valign;
|
||||
|
||||
ImageCell(Row<T> row, float width, Image image, boolean isCalculated) {
|
||||
super(row, width, null, isCalculated);
|
||||
this.img = image;
|
||||
if (image.getWidth() > getInnerWidth()) {
|
||||
scaleToFit();
|
||||
}
|
||||
this.align = HorizontalAlignment.LEFT;
|
||||
this.valign = VerticalAlignment.TOP;
|
||||
}
|
||||
|
||||
public void scaleToFit() {
|
||||
img = img.scaleByWidth(getInnerWidth());
|
||||
}
|
||||
|
||||
ImageCell(Row<T> row, float width, Image image, boolean isCalculated, HorizontalAlignment align,
|
||||
VerticalAlignment valign) {
|
||||
super(row, width, null, isCalculated, align, valign);
|
||||
this.img = image;
|
||||
if (image.getWidth() > getInnerWidth()) {
|
||||
scaleToFit();
|
||||
}
|
||||
this.align = align;
|
||||
this.valign = valign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextHeight() {
|
||||
return img.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHorizontalFreeSpace() {
|
||||
return getInnerWidth() - img.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVerticalFreeSpace() {
|
||||
return getInnerHeight() - img.getHeight();
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return img;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Utility methods for images
|
||||
* </p>
|
||||
*
|
||||
* @author mkuehne
|
||||
* @author hstimac
|
||||
*/
|
||||
public class ImageUtils {
|
||||
|
||||
// utility class, no instance needed
|
||||
private ImageUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple reading image from file
|
||||
* </p>
|
||||
*
|
||||
* @param imageFile {@link File} from which image will be loaded
|
||||
* @return {@link Image}
|
||||
* @throws IOException if loading image fails
|
||||
*/
|
||||
public static Image readImage(File imageFile) throws IOException {
|
||||
final BufferedImage bufferedImage = ImageIO.read(imageFile);
|
||||
return new Image(bufferedImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provide an ability to scale {@link Image} on desired {@link Dimension}
|
||||
* </p>
|
||||
*
|
||||
* @param imageWidth Original image width
|
||||
* @param imageHeight Original image height
|
||||
* @param boundWidth Desired image width
|
||||
* @param boundHeight Desired image height
|
||||
* @return {@code Array} with image dimension. First value is width and second is height.
|
||||
*/
|
||||
public static float[] getScaledDimension(float imageWidth, float imageHeight, float boundWidth, float boundHeight) {
|
||||
float newImageWidth = imageWidth;
|
||||
float newImageHeight = imageHeight;
|
||||
|
||||
// first check if we need to scale width
|
||||
if (imageWidth > boundWidth) {
|
||||
newImageWidth = boundWidth;
|
||||
// scale height to maintain aspect ratio
|
||||
newImageHeight = (newImageWidth * imageHeight) / imageWidth;
|
||||
}
|
||||
|
||||
// then check if the new height is also bigger than expected
|
||||
if (newImageHeight > boundHeight) {
|
||||
newImageHeight = boundHeight;
|
||||
// scale width to maintain aspect ratio
|
||||
newImageWidth = (newImageHeight * imageWidth) / imageHeight;
|
||||
}
|
||||
|
||||
float[] imageDimension = {newImageWidth, newImageHeight};
|
||||
return imageDimension;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* The <code>LineStyle</code> class defines a basic set of rendering attributes
|
||||
* for lines.
|
||||
* </p>
|
||||
*/
|
||||
public class LineStyle {
|
||||
|
||||
private final Color color;
|
||||
|
||||
private final float width;
|
||||
|
||||
private float[] dashArray;
|
||||
|
||||
private float dashPhase;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Simple constructor for setting line {@link Color} and line width
|
||||
* </p>
|
||||
*
|
||||
* @param color The line {@link Color}
|
||||
* @param width The line width
|
||||
*/
|
||||
public LineStyle(final Color color, final float width) {
|
||||
this.color = color;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to produce dotted line.
|
||||
* </p>
|
||||
*
|
||||
* @param color The {@link Color} of the line
|
||||
* @param width The line width
|
||||
* @return new styled line
|
||||
*/
|
||||
public static LineStyle produceDotted(final Color color, final int width) {
|
||||
final LineStyle line = new LineStyle(color, width);
|
||||
line.dashArray = new float[]{1.0f};
|
||||
line.dashPhase = 0.0f;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to produce dashed line.
|
||||
* </p>
|
||||
*
|
||||
* @param color The {@link Color} of the line
|
||||
* @param width The line width
|
||||
* @return new styled line
|
||||
*/
|
||||
public static LineStyle produceDashed(final Color color, final int width) {
|
||||
return produceDashed(color, width, new float[]{5.0f}, 0.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param color The {@link Color} of the line
|
||||
* @param width The line width
|
||||
* @param dashArray Mimics the behavior of {@link BasicStroke#getDashArray()}
|
||||
* @param dashPhase Mimics the behavior of {@link BasicStroke#getDashPhase()}
|
||||
* @return new styled line
|
||||
*/
|
||||
public static LineStyle produceDashed(final Color color, final int width, final float[] dashArray,
|
||||
final float dashPhase) {
|
||||
final LineStyle line = new LineStyle(color, width);
|
||||
line.dashArray = dashArray;
|
||||
line.dashPhase = dashPhase;
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public float[] getDashArray() {
|
||||
return dashArray;
|
||||
}
|
||||
|
||||
public float getDashPhase() {
|
||||
return dashPhase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 89 * hash + Objects.hashCode(this.color);
|
||||
hash = 89 * hash + Float.floatToIntBits(this.width);
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final LineStyle other = (LineStyle) obj;
|
||||
if (!Objects.equals(this.color, other.color)) {
|
||||
return false;
|
||||
}
|
||||
return Float.floatToIntBits(this.width) == Float.floatToIntBits(other.width);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Utility methods for {@link PDPageContentStream}
|
||||
* </p>
|
||||
*/
|
||||
public final class PDStreamUtils {
|
||||
|
||||
private PDStreamUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to write on a {@link PDPageContentStream}. The text will
|
||||
* be written above Y coordinate.
|
||||
* </p>
|
||||
*
|
||||
* @param stream The {@link PDPageContentStream} where writing will be applied.
|
||||
* @param text The text which will be displayed.
|
||||
* @param font The font of the text
|
||||
* @param fontSize The font size of the text
|
||||
* @param x Start X coordinate for text.
|
||||
* @param y Start Y coordinate for text.
|
||||
* @param color Color of the text
|
||||
*/
|
||||
public static void write(final PageContentStreamOptimized stream, final String text, final PDFont font,
|
||||
final float fontSize, final float x, final float y, final Color color) {
|
||||
try {
|
||||
stream.setFont(font, fontSize);
|
||||
// we want to position our text on his baseline
|
||||
stream.newLineAt(x, y - FontUtils.getDescent(font, fontSize) - FontUtils.getHeight(font, fontSize));
|
||||
stream.setNonStrokingColor(color);
|
||||
stream.showText(text);
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException("Unable to write text", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to draw rectangle for debugging purposes.
|
||||
* </p>
|
||||
*
|
||||
* @param stream The {@link PDPageContentStream} where drawing will be applied.
|
||||
* @param x Start X coordinate for rectangle.
|
||||
* @param y Start Y coordinate for rectangle.
|
||||
* @param width Width of rectangle
|
||||
* @param height Height of rectangle
|
||||
* @param color Color of the text
|
||||
*/
|
||||
public static void rect(final PageContentStreamOptimized stream, final float x, final float y, final float width,
|
||||
final float height, final Color color) {
|
||||
try {
|
||||
stream.setNonStrokingColor(color);
|
||||
// negative height because we want to draw down (not up!)
|
||||
stream.addRect(x, y, width, -height);
|
||||
stream.fill();
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException("Unable to draw rectangle", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to draw font metrics (font height, font ascent, font
|
||||
* descent).
|
||||
* </p>
|
||||
*
|
||||
* @param stream The {@link PDPageContentStream} where drawing will be applied.
|
||||
* @param x Start X coordinate for rectangle.
|
||||
* @param y Start Y coordinate for rectangle.
|
||||
* @param font {@link PDFont} from which will be obtained font metrics
|
||||
* @param fontSize Font size
|
||||
*/
|
||||
public static void rectFontMetrics(final PageContentStreamOptimized stream, final float x, final float y,
|
||||
final PDFont font, final float fontSize) {
|
||||
// height
|
||||
PDStreamUtils.rect(stream, x, y, 3, FontUtils.getHeight(font, fontSize), Color.BLUE);
|
||||
// ascent
|
||||
PDStreamUtils.rect(stream, x + 3, y, 3, FontUtils.getAscent(font, fontSize), Color.CYAN);
|
||||
// descent
|
||||
PDStreamUtils.rect(stream, x + 3, y - FontUtils.getHeight(font, fontSize), 3, FontUtils.getDescent(font, 14),
|
||||
Color.GREEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides ability to set different line styles (line width, dotted line,
|
||||
* dashed line)
|
||||
* </p>
|
||||
*
|
||||
* @param stream The {@link PDPageContentStream} where drawing will be applied.
|
||||
* @param line The {@link LineStyle} that would be applied
|
||||
* @throws IOException If the content stream could not be written or the line color cannot be retrieved.
|
||||
*/
|
||||
public static void setLineStyles(final PageContentStreamOptimized stream, final LineStyle line) throws IOException {
|
||||
stream.setStrokingColor(line.getColor());
|
||||
stream.setLineWidth(line.getWidth());
|
||||
stream.setLineCapStyle(0);
|
||||
if (line.getDashArray() != null) {
|
||||
stream.setLineDashPattern(line.getDashArray(), line.getDashPhase());
|
||||
} else {
|
||||
stream.setLineDashPattern(new float[]{}, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class PageContentStreamOptimized {
|
||||
|
||||
private static final Matrix ROTATION = Matrix.getRotateInstance(Math.PI * 0.5, 0, 0);
|
||||
|
||||
private final PDPageContentStream pageContentStream;
|
||||
private boolean textMode;
|
||||
private float textCursorAbsoluteX;
|
||||
private float textCursorAbsoluteY;
|
||||
private boolean rotated;
|
||||
|
||||
public PageContentStreamOptimized(PDPageContentStream pageContentStream) {
|
||||
this.pageContentStream = pageContentStream;
|
||||
}
|
||||
|
||||
public void setRotated(boolean rotated) throws IOException {
|
||||
if (this.rotated == rotated) {
|
||||
return;
|
||||
}
|
||||
if (rotated) {
|
||||
if (textMode) {
|
||||
pageContentStream.setTextMatrix(ROTATION);
|
||||
textCursorAbsoluteX = 0;
|
||||
textCursorAbsoluteY = 0;
|
||||
}
|
||||
} else {
|
||||
endText();
|
||||
}
|
||||
this.rotated = rotated;
|
||||
}
|
||||
|
||||
public void beginText() throws IOException {
|
||||
if (!textMode) {
|
||||
pageContentStream.beginText();
|
||||
if (rotated) {
|
||||
pageContentStream.setTextMatrix(ROTATION);
|
||||
}
|
||||
textMode = true;
|
||||
textCursorAbsoluteX = 0;
|
||||
textCursorAbsoluteY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void endText() throws IOException {
|
||||
if (textMode) {
|
||||
pageContentStream.endText();
|
||||
textMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
private PDFont currentFont;
|
||||
private float currentFontSize;
|
||||
|
||||
public void setFont(PDFont font, float fontSize) throws IOException {
|
||||
if (font != currentFont || fontSize != currentFontSize) {
|
||||
pageContentStream.setFont(font, fontSize);
|
||||
currentFont = font;
|
||||
currentFontSize = fontSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void showText(String text) throws IOException {
|
||||
beginText();
|
||||
pageContentStream.showText(text);
|
||||
}
|
||||
|
||||
public void newLineAt(float tx, float ty) throws IOException {
|
||||
beginText();
|
||||
float dx = tx - textCursorAbsoluteX;
|
||||
float dy = ty - textCursorAbsoluteY;
|
||||
if (rotated) {
|
||||
pageContentStream.newLineAtOffset(dy, -dx);
|
||||
} else {
|
||||
pageContentStream.newLineAtOffset(dx, dy);
|
||||
}
|
||||
textCursorAbsoluteX = tx;
|
||||
textCursorAbsoluteY = ty;
|
||||
}
|
||||
|
||||
public void drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException {
|
||||
endText();
|
||||
pageContentStream.drawImage(image, x, y, width, height);
|
||||
}
|
||||
|
||||
private Color currentStrokingColor;
|
||||
|
||||
public void setStrokingColor(Color color) throws IOException {
|
||||
if (color != currentStrokingColor) {
|
||||
pageContentStream.setStrokingColor(color);
|
||||
currentStrokingColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
private Color currentNonStrokingColor;
|
||||
|
||||
public void setNonStrokingColor(Color color) throws IOException {
|
||||
if (color != currentNonStrokingColor) {
|
||||
pageContentStream.setNonStrokingColor(color);
|
||||
currentNonStrokingColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public void addRect(float x, float y, float width, float height) throws IOException {
|
||||
endText();
|
||||
pageContentStream.addRect(x, y, width, height);
|
||||
}
|
||||
|
||||
public void moveTo(float x, float y) throws IOException {
|
||||
endText();
|
||||
pageContentStream.moveTo(x, y);
|
||||
}
|
||||
|
||||
public void lineTo(float x, float y) throws IOException {
|
||||
endText();
|
||||
pageContentStream.lineTo(x, y);
|
||||
}
|
||||
|
||||
public void stroke() throws IOException {
|
||||
endText();
|
||||
pageContentStream.stroke();
|
||||
}
|
||||
|
||||
public void fill() throws IOException {
|
||||
endText();
|
||||
pageContentStream.fill();
|
||||
}
|
||||
|
||||
private float currentLineWidth = -1;
|
||||
|
||||
public void setLineWidth(float lineWidth) throws IOException {
|
||||
if (lineWidth != currentLineWidth) {
|
||||
endText();
|
||||
pageContentStream.setLineWidth(lineWidth);
|
||||
currentLineWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private int currentLineCapStyle = -1;
|
||||
|
||||
public void setLineCapStyle(int lineCapStyle) throws IOException {
|
||||
if (lineCapStyle != currentLineCapStyle) {
|
||||
endText();
|
||||
pageContentStream.setLineCapStyle(lineCapStyle);
|
||||
currentLineCapStyle = lineCapStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private float[] currentLineDashPattern;
|
||||
private float currentLineDashPhase;
|
||||
|
||||
public void setLineDashPattern(float[] pattern, float phase) throws IOException {
|
||||
if ((pattern != currentLineDashPattern &&
|
||||
!Arrays.equals(pattern, currentLineDashPattern)) || phase != currentLineDashPhase) {
|
||||
endText();
|
||||
pageContentStream.setLineDashPattern(pattern, phase);
|
||||
currentLineDashPattern = pattern;
|
||||
currentLineDashPhase = phase;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
endText();
|
||||
pageContentStream.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
|
||||
public interface PageProvider<T extends PDPage> {
|
||||
|
||||
T createPage();
|
||||
|
||||
T nextPage();
|
||||
|
||||
T previousPage();
|
||||
|
||||
PDDocument getDocument();
|
||||
}
|
|
@ -0,0 +1,740 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
public class Paragraph {
|
||||
|
||||
private float width;
|
||||
private final String text;
|
||||
private float fontSize;
|
||||
private PDFont font;
|
||||
private final PDFont fontBold;
|
||||
private final PDFont fontItalic;
|
||||
private final PDFont fontBoldItalic;
|
||||
private final WrappingFunction wrappingFunction;
|
||||
private HorizontalAlignment align;
|
||||
private final TextType textType;
|
||||
private Color color;
|
||||
private float lineSpacing;
|
||||
|
||||
private final static int DEFAULT_TAB = 4;
|
||||
private final static int DEFAULT_TAB_AND_BULLET = 6;
|
||||
private final static int BULLET_SPACE = 2;
|
||||
|
||||
private boolean drawDebug;
|
||||
private final Map<Integer, Float> lineWidths = new HashMap<>();
|
||||
private final Map<Integer, List<Token>> mapLineTokens = new LinkedHashMap<>();
|
||||
private float maxLineWidth = Integer.MIN_VALUE;
|
||||
private List<Token> tokens;
|
||||
private List<String> lines;
|
||||
private Float spaceWidth;
|
||||
|
||||
public Paragraph(String text, PDFont font, float fontSize, float width, final HorizontalAlignment align) {
|
||||
this(text, font, fontSize, width, align, null);
|
||||
}
|
||||
|
||||
// This function exists only to preserve backwards compatibility for
|
||||
// the getWrappingFunction() method; it has been replaced with a faster implementation in the Tokenizer
|
||||
private static final WrappingFunction DEFAULT_WRAP_FUNC = new WrappingFunction() {
|
||||
@Override
|
||||
public String[] getLines(String t) {
|
||||
return t.split("(?<=\\s|-|@|,|\\.|:|;)");
|
||||
}
|
||||
};
|
||||
|
||||
public Paragraph(String text, PDFont font, int fontSize, int width) {
|
||||
this(text, font, fontSize, width, HorizontalAlignment.LEFT, null);
|
||||
}
|
||||
|
||||
public Paragraph(String text, PDFont font, float fontSize, float width, final HorizontalAlignment align,
|
||||
WrappingFunction wrappingFunction) {
|
||||
this(text, font, fontSize, width, align, Color.BLACK, null, wrappingFunction);
|
||||
}
|
||||
|
||||
public Paragraph(String text, PDFont font, float fontSize, float width, final HorizontalAlignment align,
|
||||
final Color color, final TextType textType, WrappingFunction wrappingFunction) {
|
||||
this(text, font, fontSize, width, align, color, textType, wrappingFunction, 1);
|
||||
}
|
||||
|
||||
public Paragraph(String text, PDFont font, float fontSize, float width, final HorizontalAlignment align,
|
||||
final Color color, final TextType textType, WrappingFunction wrappingFunction, float lineSpacing) {
|
||||
this.color = color;
|
||||
this.text = text;
|
||||
this.font = font;
|
||||
// check if we have different default font for italic and bold text
|
||||
if (FontUtils.getDefaultfonts().isEmpty()) {
|
||||
fontBold = PDType1Font.HELVETICA_BOLD;
|
||||
fontItalic = PDType1Font.HELVETICA_OBLIQUE;
|
||||
fontBoldItalic = PDType1Font.HELVETICA_BOLD_OBLIQUE;
|
||||
} else {
|
||||
fontBold = FontUtils.getDefaultfonts().get("fontBold");
|
||||
fontBoldItalic = FontUtils.getDefaultfonts().get("fontBoldItalic");
|
||||
fontItalic = FontUtils.getDefaultfonts().get("fontItalic");
|
||||
}
|
||||
this.fontSize = fontSize;
|
||||
this.width = width;
|
||||
this.textType = textType;
|
||||
this.setAlign(align);
|
||||
this.wrappingFunction = wrappingFunction;
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
|
||||
public List<String> getLines() {
|
||||
// memoize this function because it is very expensive
|
||||
if (lines != null) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
final List<String> result = new ArrayList<>();
|
||||
|
||||
// text and wrappingFunction are immutable, so we only ever need to compute tokens once
|
||||
if (tokens == null) {
|
||||
tokens = Tokenizer.tokenize(text, wrappingFunction);
|
||||
}
|
||||
|
||||
int lineCounter = 0;
|
||||
boolean italic = false;
|
||||
boolean bold = false;
|
||||
boolean listElement = false;
|
||||
PDFont currentFont = font;
|
||||
int orderListElement = 1;
|
||||
int numberOfOrderedLists = 0;
|
||||
int listLevel = 0;
|
||||
Stack<HTMLListNode> stack = new Stack<>();
|
||||
|
||||
final PipelineLayer textInLine = new PipelineLayer();
|
||||
final PipelineLayer sinceLastWrapPoint = new PipelineLayer();
|
||||
|
||||
for (final Token token : tokens) {
|
||||
switch (token.getType()) {
|
||||
case OPEN_TAG:
|
||||
if (isBold(token)) {
|
||||
bold = true;
|
||||
currentFont = getFont(bold, italic);
|
||||
} else if (isItalic(token)) {
|
||||
italic = true;
|
||||
currentFont = getFont(bold, italic);
|
||||
} else if (isList(token)) {
|
||||
listLevel++;
|
||||
if (token.getData().equals("ol")) {
|
||||
numberOfOrderedLists++;
|
||||
if (listLevel > 1) {
|
||||
stack.add(new HTMLListNode(orderListElement - 1, stack.isEmpty() ? (orderListElement - 1) + "." : stack.peek().getValue() + (orderListElement - 1) + "."));
|
||||
}
|
||||
orderListElement = 1;
|
||||
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
// check if you have some text before this list, if you don't then you really don't need extra line break for that
|
||||
if (textInLine.trimmedWidth() > 0) {
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
}
|
||||
} else if (token.getData().equals("ul")) {
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
// check if you have some text before this list, if you don't then you really don't need extra line break for that
|
||||
if (textInLine.trimmedWidth() > 0) {
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
sinceLastWrapPoint.push(token);
|
||||
break;
|
||||
case CLOSE_TAG:
|
||||
if (isBold(token)) {
|
||||
bold = false;
|
||||
currentFont = getFont(bold, italic);
|
||||
sinceLastWrapPoint.push(token);
|
||||
} else if (isItalic(token)) {
|
||||
italic = false;
|
||||
currentFont = getFont(bold, italic);
|
||||
sinceLastWrapPoint.push(token);
|
||||
} else if (isList(token)) {
|
||||
listLevel--;
|
||||
if (token.getData().equals("ol")) {
|
||||
numberOfOrderedLists--;
|
||||
// reset elements
|
||||
if (numberOfOrderedLists > 0) {
|
||||
orderListElement = stack.peek().getOrderingNumber() + 1;
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
// ensure extra space after each lists
|
||||
// no need to worry about current line text because last closing <li> tag already done that
|
||||
if (listLevel == 0) {
|
||||
result.add(" ");
|
||||
lineWidths.put(lineCounter, 0.0f);
|
||||
mapLineTokens.put(lineCounter, new ArrayList<Token>());
|
||||
lineCounter++;
|
||||
}
|
||||
} else if (isListElement(token)) {
|
||||
// wrap at last wrap point?
|
||||
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
// wrapping at last wrap point
|
||||
if (numberOfOrderedLists > 0) {
|
||||
String orderingNumber = stack.isEmpty() ? orderListElement + "." : stack.pop().getValue() + ".";
|
||||
stack.add(new HTMLListNode(orderListElement, orderingNumber));
|
||||
try {
|
||||
float tab = indentLevel(DEFAULT_TAB);
|
||||
float orderingNumberAndTab = font.getStringWidth(orderingNumber) + tab;
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String
|
||||
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
orderListElement++;
|
||||
} else {
|
||||
try {
|
||||
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
|
||||
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf(tabBullet / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
}
|
||||
// wrapping at this must-have wrap point
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
listElement = false;
|
||||
}
|
||||
if (isParagraph(token)) {
|
||||
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
lineCounter++;
|
||||
textInLine.reset();
|
||||
}
|
||||
// wrapping at this must-have wrap point
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
|
||||
// extra spacing because it's a paragraph
|
||||
result.add(" ");
|
||||
lineWidths.put(lineCounter, 0.0f);
|
||||
mapLineTokens.put(lineCounter, new ArrayList<Token>());
|
||||
lineCounter++;
|
||||
}
|
||||
break;
|
||||
case POSSIBLE_WRAP_POINT:
|
||||
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
|
||||
// this is our line
|
||||
if (!textInLine.isEmpty()) {
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
lineCounter++;
|
||||
textInLine.reset();
|
||||
}
|
||||
// wrapping at last wrap point
|
||||
if (listElement) {
|
||||
if (numberOfOrderedLists > 0) {
|
||||
try {
|
||||
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB) : indentLevel(DEFAULT_TAB);
|
||||
String orderingNumber = stack.isEmpty() ? orderListElement + "." : stack.peek().getValue() + "." + (orderListElement - 1) + ".";
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf((tab + font.getStringWidth(orderingNumber)) / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behavior
|
||||
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf(tabBullet / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
} else {
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
}
|
||||
break;
|
||||
case WRAP_POINT:
|
||||
// wrap at last wrap point?
|
||||
if (textInLine.width() + sinceLastWrapPoint.trimmedWidth() > width) {
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
// wrapping at last wrap point
|
||||
if (listElement) {
|
||||
if (!getAlign().equals(HorizontalAlignment.LEFT)) {
|
||||
listLevel = 0;
|
||||
}
|
||||
if (numberOfOrderedLists > 0) {
|
||||
// String orderingNumber = String.valueOf(orderListElement) + ". ";
|
||||
String orderingNumber = stack.isEmpty() ? "1" + "." : stack.pop().getValue() + ". ";
|
||||
try {
|
||||
float tab = indentLevel(DEFAULT_TAB);
|
||||
float orderingNumberAndTab = font.getStringWidth(orderingNumber) + tab;
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String
|
||||
.valueOf(orderingNumberAndTab / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
|
||||
float tabBullet = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB_AND_BULLET) : indentLevel(DEFAULT_TAB);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf(tabBullet / 1000 * getFontSize())));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
}
|
||||
if (isParagraph(token)) {
|
||||
// check if you have some text before this paragraph, if you don't then you really don't need extra line break for that
|
||||
if (textInLine.trimmedWidth() > 0) {
|
||||
// extra spacing because it's a paragraph
|
||||
result.add(" ");
|
||||
lineWidths.put(lineCounter, 0.0f);
|
||||
mapLineTokens.put(lineCounter, new ArrayList<Token>());
|
||||
lineCounter++;
|
||||
}
|
||||
} else if (isListElement(token)) {
|
||||
listElement = true;
|
||||
// token padding, token bullet
|
||||
try {
|
||||
// if it's not left aligned then ignore list and list element and deal with it as normal text where <li> mimic <br> behaviour
|
||||
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB) : indentLevel(DEFAULT_TAB);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf(tab / 1000 * getFontSize())));
|
||||
if (numberOfOrderedLists > 0) {
|
||||
// if it's ordering list then move depending on your: ordering number + ". "
|
||||
String orderingNumber;
|
||||
if (listLevel > 1) {
|
||||
orderingNumber = stack.peek().getValue() + orderListElement + ". ";
|
||||
} else {
|
||||
orderingNumber = orderListElement + ". ";
|
||||
}
|
||||
textInLine.push(currentFont, fontSize, Token.text(TokenType.ORDERING, orderingNumber));
|
||||
orderListElement++;
|
||||
} else {
|
||||
// if it's unordered list then just move by bullet character (take care of alignment!)
|
||||
textInLine.push(currentFont, fontSize, Token.text(TokenType.BULLET, " "));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
// wrapping at this must-have wrap point
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
if (listLevel > 0) {
|
||||
// preserve current indent
|
||||
try {
|
||||
if (numberOfOrderedLists > 0) {
|
||||
float tab = getAlign().equals(HorizontalAlignment.LEFT) ? indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0)) : indentLevel(DEFAULT_TAB);
|
||||
// if it's ordering list then move depending on your: ordering number + ". "
|
||||
String orderingNumber;
|
||||
if (listLevel > 1) {
|
||||
orderingNumber = stack.peek().getValue() + orderListElement + ". ";
|
||||
} else {
|
||||
orderingNumber = orderListElement + ". ";
|
||||
}
|
||||
float tabAndOrderingNumber = tab + font.getStringWidth(orderingNumber);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING, String.valueOf(tabAndOrderingNumber / 1000 * getFontSize())));
|
||||
orderListElement++;
|
||||
} else {
|
||||
if (getAlign().equals(HorizontalAlignment.LEFT)) {
|
||||
float tab = indentLevel(DEFAULT_TAB * Math.max(listLevel - 1, 0) + DEFAULT_TAB + BULLET_SPACE);
|
||||
textInLine.push(currentFont, fontSize, new Token(TokenType.PADDING,
|
||||
String.valueOf(tab / 1000 * getFontSize())));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
try {
|
||||
String word = token.getData();
|
||||
float wordWidth = token.getWidth(currentFont);
|
||||
if (wordWidth / 1000f * fontSize > width && width > font.getAverageFontWidth() / 1000f * fontSize) {
|
||||
// you need to check if you have already something in your line
|
||||
boolean alreadyTextInLine = false;
|
||||
if (textInLine.trimmedWidth() > 0) {
|
||||
alreadyTextInLine = true;
|
||||
}
|
||||
while (wordWidth / 1000f * fontSize > width) {
|
||||
float width = 0;
|
||||
float firstPartWordWidth = 0;
|
||||
float restOfTheWordWidth = 0;
|
||||
String lastTextToken = word;
|
||||
StringBuilder firstPartOfWord = new StringBuilder();
|
||||
StringBuilder restOfTheWord = new StringBuilder();
|
||||
for (int i = 0; i < lastTextToken.length(); i++) {
|
||||
char c = lastTextToken.charAt(i);
|
||||
try {
|
||||
width += (currentFont.getStringWidth(String.valueOf(c)) / 1000f * fontSize);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (alreadyTextInLine) {
|
||||
if (width < this.width - textInLine.trimmedWidth()) {
|
||||
firstPartOfWord.append(c);
|
||||
firstPartWordWidth = Math.max(width, firstPartWordWidth);
|
||||
} else {
|
||||
restOfTheWord.append(c);
|
||||
restOfTheWordWidth = Math.max(width, restOfTheWordWidth);
|
||||
}
|
||||
} else {
|
||||
if (width < this.width) {
|
||||
firstPartOfWord.append(c);
|
||||
firstPartWordWidth = Math.max(width, firstPartWordWidth);
|
||||
} else {
|
||||
if (i == 0) {
|
||||
firstPartOfWord.append(c);
|
||||
for (int j = 1; j < lastTextToken.length(); j++) {
|
||||
restOfTheWord.append(lastTextToken.charAt(j));
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
restOfTheWord.append(c);
|
||||
restOfTheWordWidth = Math.max(width, restOfTheWordWidth);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// reset
|
||||
alreadyTextInLine = false;
|
||||
sinceLastWrapPoint.push(currentFont, fontSize,
|
||||
Token.text(TokenType.TEXT, firstPartOfWord.toString()));
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
// this is our line
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
textInLine.reset();
|
||||
lineCounter++;
|
||||
word = restOfTheWord.toString();
|
||||
wordWidth = currentFont.getStringWidth(word);
|
||||
}
|
||||
sinceLastWrapPoint.push(currentFont, fontSize, Token.text(TokenType.TEXT, word));
|
||||
} else {
|
||||
sinceLastWrapPoint.push(currentFont, fontSize, token);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sinceLastWrapPoint.trimmedWidth() + textInLine.trimmedWidth() > 0) {
|
||||
textInLine.push(sinceLastWrapPoint);
|
||||
result.add(textInLine.trimmedText());
|
||||
lineWidths.put(lineCounter, textInLine.trimmedWidth());
|
||||
mapLineTokens.put(lineCounter, textInLine.tokens());
|
||||
maxLineWidth = Math.max(maxLineWidth, textInLine.trimmedWidth());
|
||||
}
|
||||
|
||||
lines = result;
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private static boolean isItalic(final Token token) {
|
||||
return "i".equals(token.getData());
|
||||
}
|
||||
|
||||
private static boolean isBold(final Token token) {
|
||||
return "b".equals(token.getData());
|
||||
}
|
||||
|
||||
private static boolean isParagraph(final Token token) {
|
||||
return "p".equals(token.getData());
|
||||
}
|
||||
|
||||
private static boolean isListElement(final Token token) {
|
||||
return "li".equals(token.getData());
|
||||
}
|
||||
|
||||
private static boolean isList(final Token token) {
|
||||
return "ul".equals(token.getData()) || "ol".equals(token.getData());
|
||||
}
|
||||
|
||||
private float indentLevel(int numberOfSpaces) throws IOException {
|
||||
if (spaceWidth == null) {
|
||||
spaceWidth = font.getSpaceWidth();
|
||||
}
|
||||
return numberOfSpaces * spaceWidth;
|
||||
}
|
||||
|
||||
public PDFont getFont(boolean isBold, boolean isItalic) {
|
||||
if (isBold) {
|
||||
if (isItalic) {
|
||||
return fontBoldItalic;
|
||||
} else {
|
||||
return fontBold;
|
||||
}
|
||||
} else if (isItalic) {
|
||||
return fontItalic;
|
||||
} else {
|
||||
return font;
|
||||
}
|
||||
}
|
||||
|
||||
public float write(final PageContentStreamOptimized stream, float cursorX, float cursorY) {
|
||||
if (drawDebug) {
|
||||
PDStreamUtils.rectFontMetrics(stream, cursorX, cursorY, font, fontSize);
|
||||
|
||||
// width
|
||||
PDStreamUtils.rect(stream, cursorX, cursorY, width, 1, Color.RED);
|
||||
}
|
||||
|
||||
for (String line : getLines()) {
|
||||
line = line.trim();
|
||||
|
||||
float textX = cursorX;
|
||||
switch (align) {
|
||||
case CENTER:
|
||||
textX += getHorizontalFreeSpace(line) / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
textX += getHorizontalFreeSpace(line);
|
||||
break;
|
||||
}
|
||||
|
||||
PDStreamUtils.write(stream, line, font, fontSize, textX, cursorY, color);
|
||||
|
||||
if (textType != null) {
|
||||
switch (textType) {
|
||||
case HIGHLIGHT:
|
||||
case SQUIGGLY:
|
||||
case STRIKEOUT:
|
||||
throw new UnsupportedOperationException("Not implemented.");
|
||||
case UNDERLINE:
|
||||
float y = (float) (cursorY - FontUtils.getHeight(font, fontSize)
|
||||
- FontUtils.getDescent(font, fontSize) - 1.5);
|
||||
try {
|
||||
float titleWidth = font.getStringWidth(line) / 1000 * fontSize;
|
||||
stream.moveTo(textX, y);
|
||||
stream.lineTo(textX + titleWidth, y);
|
||||
stream.stroke();
|
||||
} catch (final IOException e) {
|
||||
throw new IllegalStateException("Unable to underline text", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// move one "line" down
|
||||
cursorY -= getFontHeight();
|
||||
}
|
||||
|
||||
return cursorY;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
if (getLines().size() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (getLines().size() - 1) * getLineSpacing() * getFontHeight() + getFontHeight();
|
||||
}
|
||||
}
|
||||
|
||||
public float getFontHeight() {
|
||||
return FontUtils.getHeight(font, fontSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return current font width
|
||||
* @deprecated This method will be removed in a future release
|
||||
*/
|
||||
@Deprecated
|
||||
public float getFontWidth() {
|
||||
return font.getFontDescriptor().getFontBoundingBox().getWidth() / 1000 * fontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param width Paragraph's width
|
||||
* @return {@link Paragraph} with designated width
|
||||
* @deprecated This method will be removed in a future release
|
||||
*/
|
||||
@Deprecated
|
||||
public Paragraph withWidth(int width) {
|
||||
invalidateLineWrapping();
|
||||
this.width = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param font {@link PDFont} for {@link Paragraph}
|
||||
* @param fontSize font size for {@link Paragraph}
|
||||
* @return {@link Paragraph} with designated font and font size
|
||||
* @deprecated This method will be removed in a future release
|
||||
*/
|
||||
@Deprecated
|
||||
public Paragraph withFont(PDFont font, int fontSize) {
|
||||
invalidateLineWrapping();
|
||||
this.spaceWidth = null;
|
||||
this.font = font;
|
||||
this.fontSize = fontSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
// font, fontSize, width, and align are non-final and used in getLines(),
|
||||
// so if they are mutated, getLines() needs to be recomputed
|
||||
private void invalidateLineWrapping() {
|
||||
lines = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* /**
|
||||
*
|
||||
* @param color {@code int} rgb value for color
|
||||
* @return Paragraph's {@link Color}
|
||||
* @deprecated This method will be removed in a future release
|
||||
*/
|
||||
@Deprecated
|
||||
public Paragraph withColor(int color) {
|
||||
this.color = new Color(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Paragraph's {@link Color}
|
||||
* @deprecated This method will be replaced by
|
||||
* {@code public Color getColor()} in a future release
|
||||
*/
|
||||
@Deprecated
|
||||
public int getColor() {
|
||||
return color.getRGB();
|
||||
}
|
||||
|
||||
private float getHorizontalFreeSpace(final String text) {
|
||||
try {
|
||||
final float tw = font.getStringWidth(text.trim()) / 1000 * fontSize;
|
||||
return width - tw;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Unable to calculate text width", e);
|
||||
}
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public PDFont getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public HorizontalAlignment getAlign() {
|
||||
return align;
|
||||
}
|
||||
|
||||
public void setAlign(HorizontalAlignment align) {
|
||||
invalidateLineWrapping();
|
||||
this.align = align;
|
||||
}
|
||||
|
||||
public boolean isDrawDebug() {
|
||||
return drawDebug;
|
||||
}
|
||||
|
||||
public void setDrawDebug(boolean drawDebug) {
|
||||
this.drawDebug = drawDebug;
|
||||
}
|
||||
|
||||
public WrappingFunction getWrappingFunction() {
|
||||
return wrappingFunction == null ? DEFAULT_WRAP_FUNC : wrappingFunction;
|
||||
}
|
||||
|
||||
public float getMaxLineWidth() {
|
||||
return maxLineWidth;
|
||||
}
|
||||
|
||||
public float getLineWidth(int key) {
|
||||
return lineWidths.get(key);
|
||||
}
|
||||
|
||||
public Map<Integer, List<Token>> getMapLineTokens() {
|
||||
return mapLineTokens;
|
||||
}
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
public void setLineSpacing(float lineSpacing) {
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Markus Kühne
|
||||
*/
|
||||
|
||||
public class PipelineLayer {
|
||||
|
||||
private static String rtrim(String s) {
|
||||
int len = s.length();
|
||||
while ((len > 0) && (s.charAt(len - 1) <= ' ')) {
|
||||
len--;
|
||||
}
|
||||
if (len == s.length()) {
|
||||
return s;
|
||||
}
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
return s.substring(0, len);
|
||||
}
|
||||
|
||||
private final StringBuilder text = new StringBuilder();
|
||||
|
||||
private String lastTextToken = "";
|
||||
|
||||
private final List<Token> tokens = new ArrayList<>();
|
||||
|
||||
private String trimmedLastTextToken = "";
|
||||
|
||||
private float width;
|
||||
|
||||
private float widthLastToken;
|
||||
|
||||
private float widthTrimmedLastToken;
|
||||
|
||||
private float widthCurrentText;
|
||||
|
||||
public boolean isEmpty() {
|
||||
return tokens.isEmpty();
|
||||
}
|
||||
|
||||
public void push(final Token token) {
|
||||
tokens.add(token);
|
||||
}
|
||||
|
||||
public void push(final PDFont font, final float fontSize, final Token token) throws IOException {
|
||||
if (token.getType().equals(TokenType.PADDING)) {
|
||||
width += Float.parseFloat(token.getData());
|
||||
}
|
||||
if (token.getType().equals(TokenType.BULLET)) {
|
||||
// just appending one space because our bullet width will be wide as one character of current font
|
||||
text.append(token.getData());
|
||||
width += (token.getWidth(font) / 1000f * fontSize);
|
||||
}
|
||||
|
||||
if (token.getType().equals(TokenType.ORDERING)) {
|
||||
// just appending one space because our bullet width will be wide as one character of current font
|
||||
text.append(token.getData());
|
||||
width += (token.getWidth(font) / 1000f * fontSize);
|
||||
}
|
||||
|
||||
if (token.getType().equals(TokenType.TEXT)) {
|
||||
text.append(lastTextToken);
|
||||
width += widthLastToken;
|
||||
lastTextToken = token.getData();
|
||||
trimmedLastTextToken = rtrim(lastTextToken);
|
||||
widthLastToken = token.getWidth(font) / 1000f * fontSize;
|
||||
|
||||
if (trimmedLastTextToken.length() == lastTextToken.length()) {
|
||||
widthTrimmedLastToken = widthLastToken;
|
||||
} else {
|
||||
widthTrimmedLastToken = (font.getStringWidth(trimmedLastTextToken) / 1000f * fontSize);
|
||||
}
|
||||
|
||||
widthCurrentText = text.length() == 0 ? 0 :
|
||||
(font.getStringWidth(text.toString()) / 1000f * fontSize);
|
||||
}
|
||||
|
||||
push(token);
|
||||
}
|
||||
|
||||
public void push(final PipelineLayer pipeline) {
|
||||
text.append(lastTextToken);
|
||||
width += widthLastToken;
|
||||
text.append(pipeline.text);
|
||||
if (pipeline.text.length() > 0) {
|
||||
width += pipeline.widthCurrentText;
|
||||
}
|
||||
lastTextToken = pipeline.lastTextToken;
|
||||
trimmedLastTextToken = pipeline.trimmedLastTextToken;
|
||||
widthLastToken = pipeline.widthLastToken;
|
||||
widthTrimmedLastToken = pipeline.widthTrimmedLastToken;
|
||||
tokens.addAll(pipeline.tokens);
|
||||
|
||||
pipeline.reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
text.delete(0, text.length());
|
||||
width = 0.0f;
|
||||
lastTextToken = "";
|
||||
trimmedLastTextToken = "";
|
||||
widthLastToken = 0.0f;
|
||||
widthTrimmedLastToken = 0.0f;
|
||||
tokens.clear();
|
||||
}
|
||||
|
||||
public String trimmedText() {
|
||||
return text.toString() + trimmedLastTextToken;
|
||||
}
|
||||
|
||||
public float width() {
|
||||
return width + widthLastToken;
|
||||
}
|
||||
|
||||
public float trimmedWidth() {
|
||||
return width + widthTrimmedLastToken;
|
||||
}
|
||||
|
||||
public List<Token> tokens() {
|
||||
return new ArrayList<>(tokens);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text.toString() + "(" + lastTextToken + ") [width: " + width() + ", trimmed: " + trimmedWidth() + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,262 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Row<T extends PDPage> {
|
||||
|
||||
private final Table<T> table;
|
||||
PDOutlineItem bookmark;
|
||||
List<Cell<T>> cells;
|
||||
private boolean headerRow = false;
|
||||
float height;
|
||||
private float lineSpacing = 1;
|
||||
|
||||
Row(Table<T> table, List<Cell<T>> cells, float height) {
|
||||
this.table = table;
|
||||
this.cells = cells;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
Row(Table<T> table, float height) {
|
||||
this.table = table;
|
||||
this.cells = new ArrayList<>();
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a cell with provided width, cell value and default left top
|
||||
* alignment
|
||||
* </p>
|
||||
*
|
||||
* @param width Absolute width in points or in % of table width
|
||||
* @param value Cell's value (content)
|
||||
* @return New {@link Cell}
|
||||
*/
|
||||
public Cell<T> createCell(float width, String value) {
|
||||
Cell<T> cell = new Cell<T>(this, width, value, true);
|
||||
if (headerRow) {
|
||||
// set all cell as header cell
|
||||
cell.setHeaderCell(true);
|
||||
}
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cell.setLineSpacing(lineSpacing);
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates an image cell with provided width and {@link Image}
|
||||
* </p>
|
||||
*
|
||||
* @param width Cell's width
|
||||
* @param img {@link Image} in the cell
|
||||
* @return {@link ImageCell}
|
||||
*/
|
||||
public ImageCell<T> createImageCell(float width, Image img) {
|
||||
ImageCell<T> cell = new ImageCell<>(this, width, img, true);
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public Cell<T> createImageCell(float width, Image img, HorizontalAlignment align, VerticalAlignment valign) {
|
||||
Cell<T> cell = new ImageCell<T>(this, width, img, true, align, valign);
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a table cell with provided width and table data
|
||||
* </p>
|
||||
*
|
||||
* @param width Table width
|
||||
* @param tableData Table's data (HTML table tags)
|
||||
* @param doc {@link PDDocument} where this table will be drawn
|
||||
* @param page {@link PDPage} where this table cell will be drawn
|
||||
* @param yStart Y position from which table will be drawn
|
||||
* @param pageTopMargin {@link TableCell}'s top margin
|
||||
* @param pageBottomMargin {@link TableCell}'s bottom margin
|
||||
* @return {@link TableCell} with provided width and table data
|
||||
*/
|
||||
public TableCell<T> createTableCell(float width, String tableData, PDDocument doc, PDPage page, float yStart,
|
||||
float pageTopMargin, float pageBottomMargin) throws IOException {
|
||||
TableCell<T> cell = new TableCell<T>(this, width, tableData, true, doc, page, yStart, pageTopMargin,
|
||||
pageBottomMargin);
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a cell with provided width, cell value, horizontal and vertical
|
||||
* alignment
|
||||
* </p>
|
||||
*
|
||||
* @param width Absolute width in points or in % of table width
|
||||
* @param value Cell's value (content)
|
||||
* @param align Cell's {@link HorizontalAlignment}
|
||||
* @param valign Cell's {@link VerticalAlignment}
|
||||
* @return New {@link Cell}
|
||||
*/
|
||||
public Cell<T> createCell(float width, String value, HorizontalAlignment align, VerticalAlignment valign) {
|
||||
Cell<T> cell = new Cell<T>(this, width, value, true, align, valign);
|
||||
if (headerRow) {
|
||||
// set all cell as header cell
|
||||
cell.setHeaderCell(true);
|
||||
}
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cell.setLineSpacing(lineSpacing);
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Creates a cell with the same width as the corresponding header cell
|
||||
* </p>
|
||||
*
|
||||
* @param value Cell's value (content)
|
||||
* @return new {@link Cell}
|
||||
*/
|
||||
public Cell<T> createCell(String value) {
|
||||
float headerCellWidth = table.getHeader().getCells().get(cells.size()).getWidth();
|
||||
Cell<T> cell = new Cell<T>(this, headerCellWidth, value, false);
|
||||
setBorders(cell, cells.isEmpty());
|
||||
cells.add(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Remove left border to avoid double borders from previous cell's right
|
||||
* border. In most cases left border will be removed.
|
||||
* </p>
|
||||
*
|
||||
* @param cell {@link Cell}
|
||||
* @param leftBorder boolean for drawing cell's left border. If {@code true} then
|
||||
* the left cell's border will be drawn.
|
||||
*/
|
||||
private void setBorders(final Cell<T> cell, final boolean leftBorder) {
|
||||
if (!leftBorder) {
|
||||
cell.setLeftBorderStyle(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* remove top borders of cells to avoid double borders from cells in
|
||||
* previous row
|
||||
* </p>
|
||||
*/
|
||||
void removeTopBorders() {
|
||||
for (final Cell<T> cell : cells) {
|
||||
cell.setTopBorderStyle(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Remove all borders of cells.
|
||||
* </p>
|
||||
*/
|
||||
void removeAllBorders() {
|
||||
for (final Cell<T> cell : cells) {
|
||||
cell.setBorderStyle(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Gets maximal height of the cells in current row therefore row's height.
|
||||
* </p>
|
||||
*
|
||||
* @return Row's height
|
||||
*/
|
||||
public float getHeight() {
|
||||
float maxheight = 0.0f;
|
||||
for (Cell<T> cell : this.cells) {
|
||||
float cellHeight = cell.getCellHeight();
|
||||
|
||||
if (cellHeight > maxheight) {
|
||||
maxheight = cellHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxheight > height) {
|
||||
this.height = maxheight;
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
public float getLineHeight() throws IOException {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public List<Cell<T>> getCells() {
|
||||
return cells;
|
||||
}
|
||||
|
||||
public int getColCount() {
|
||||
return cells.size();
|
||||
}
|
||||
|
||||
public void setCells(List<Cell<T>> cells) {
|
||||
this.cells = cells;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return table.getWidth();
|
||||
}
|
||||
|
||||
public PDOutlineItem getBookmark() {
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
public void setBookmark(PDOutlineItem bookmark) {
|
||||
this.bookmark = bookmark;
|
||||
}
|
||||
|
||||
protected float getLastCellExtraWidth() {
|
||||
float cellWidth = 0;
|
||||
for (Cell<T> cell : cells) {
|
||||
cellWidth += cell.getWidth();
|
||||
}
|
||||
|
||||
float lastCellExtraWidth = this.getWidth() - cellWidth;
|
||||
return lastCellExtraWidth;
|
||||
}
|
||||
|
||||
public float xEnd() {
|
||||
return table.getMargin() + getWidth();
|
||||
}
|
||||
|
||||
public boolean isHeaderRow() {
|
||||
return headerRow;
|
||||
}
|
||||
|
||||
public void setHeaderRow(boolean headerRow) {
|
||||
this.headerRow = headerRow;
|
||||
}
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
public void setLineSpacing(float lineSpacing) {
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,917 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Table<T extends PDPage> {
|
||||
|
||||
public final PDDocument document;
|
||||
private final float margin;
|
||||
|
||||
private T currentPage;
|
||||
private PageContentStreamOptimized tableContentStream;
|
||||
private List<PDOutlineItem> bookmarks;
|
||||
private final List<Row<T>> header = new ArrayList<>();
|
||||
private final List<Row<T>> rows = new ArrayList<>();
|
||||
|
||||
private final float yStartNewPage;
|
||||
private float yStart;
|
||||
private final float width;
|
||||
private final boolean drawLines;
|
||||
private final boolean drawContent;
|
||||
private final float headerBottomMargin = 4f;
|
||||
private float lineSpacing = 1f;
|
||||
|
||||
private boolean tableIsBroken = false;
|
||||
private boolean tableStartedAtNewPage = false;
|
||||
private boolean removeTopBorders = false;
|
||||
private boolean removeAllBorders = false;
|
||||
|
||||
private final PageProvider<T> pageProvider;
|
||||
|
||||
// page margins
|
||||
private final float pageTopMargin;
|
||||
private final float pageBottomMargin;
|
||||
|
||||
private boolean drawDebug;
|
||||
|
||||
/**
|
||||
* @param yStart Y position where {@link Table} will start
|
||||
* @param yStartNewPage Y position where possible new page of {@link Table} will start
|
||||
* @param pageBottomMargin bottom margin of {@link Table}
|
||||
* @param width {@link Table} width
|
||||
* @param margin {@link Table} margin
|
||||
* @param document {@link PDDocument} where {@link Table} will be drawn
|
||||
* @param currentPage current page where {@link Table} will be drawn (some tables
|
||||
* are big and can be through multiple pages)
|
||||
* @param drawLines draw {@link Table}'s borders
|
||||
* @param drawContent draw {@link Table}'s content
|
||||
* @throws IOException if fonts are not loaded correctly
|
||||
* @deprecated Use one of the constructors that pass a {@link PageProvider}
|
||||
*/
|
||||
@Deprecated
|
||||
public Table(float yStart, float yStartNewPage, float pageBottomMargin, float width, float margin,
|
||||
PDDocument document, T currentPage, boolean drawLines, boolean drawContent) throws IOException {
|
||||
this(yStart, yStartNewPage, 0, pageBottomMargin, width, margin, document, currentPage, drawLines, drawContent,
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param yStartNewPage Y position where possible new page of {@link Table} will start
|
||||
* @param pageBottomMargin bottom margin of {@link Table}
|
||||
* @param width {@link Table} width
|
||||
* @param margin {@link Table} margin
|
||||
* @param document {@link PDDocument} where {@link Table} will be drawn
|
||||
* @param drawLines draw {@link Table}'s borders
|
||||
* @param drawContent draw {@link Table}'s content
|
||||
* @throws IOException if fonts are not loaded correctly
|
||||
* @deprecated Use one of the constructors that pass a {@link PageProvider}
|
||||
*/
|
||||
@Deprecated
|
||||
public Table(float yStartNewPage, float pageBottomMargin, float width, float margin, PDDocument document,
|
||||
boolean drawLines, boolean drawContent) throws IOException {
|
||||
this(yStartNewPage, 0, pageBottomMargin, width, margin, document, drawLines, drawContent, null);
|
||||
}
|
||||
|
||||
public Table(float yStart, float yStartNewPage, float pageTopMargin, float pageBottomMargin, float width,
|
||||
float margin, PDDocument document, T currentPage, boolean drawLines, boolean drawContent,
|
||||
PageProvider<T> pageProvider) throws IOException {
|
||||
this.pageTopMargin = pageTopMargin;
|
||||
this.document = document;
|
||||
this.drawLines = drawLines;
|
||||
this.drawContent = drawContent;
|
||||
// Initialize table
|
||||
this.yStartNewPage = yStartNewPage;
|
||||
this.margin = margin;
|
||||
this.width = width;
|
||||
this.yStart = yStart;
|
||||
this.pageBottomMargin = pageBottomMargin;
|
||||
this.currentPage = currentPage;
|
||||
this.pageProvider = pageProvider;
|
||||
loadFonts();
|
||||
}
|
||||
|
||||
public Table(float yStartNewPage, float pageTopMargin, float pageBottomMargin, float width, float margin,
|
||||
PDDocument document, boolean drawLines, boolean drawContent, PageProvider<T> pageProvider)
|
||||
throws IOException {
|
||||
this.pageTopMargin = pageTopMargin;
|
||||
this.document = document;
|
||||
this.drawLines = drawLines;
|
||||
this.drawContent = drawContent;
|
||||
// Initialize table
|
||||
this.yStartNewPage = yStartNewPage;
|
||||
this.margin = margin;
|
||||
this.width = width;
|
||||
this.pageProvider = pageProvider;
|
||||
this.pageBottomMargin = pageBottomMargin;
|
||||
|
||||
// Fonts needs to be loaded before page creation
|
||||
loadFonts();
|
||||
this.currentPage = pageProvider.nextPage();
|
||||
}
|
||||
|
||||
protected abstract void loadFonts() throws IOException;
|
||||
|
||||
protected PDType0Font loadFont(String fontPath) throws IOException {
|
||||
return FontUtils.loadFont(getDocument(), fontPath);
|
||||
}
|
||||
|
||||
protected PDDocument getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public void drawTitle(String title, PDFont font, int fontSize, float tableWidth, float height, String alignment,
|
||||
float freeSpaceForPageBreak, boolean drawHeaderMargin) throws IOException {
|
||||
drawTitle(title, font, fontSize, tableWidth, height, alignment, freeSpaceForPageBreak, null, drawHeaderMargin);
|
||||
}
|
||||
|
||||
public void drawTitle(String title, PDFont font, int fontSize, float tableWidth, float height, String alignment,
|
||||
float freeSpaceForPageBreak, WrappingFunction wrappingFunction, boolean drawHeaderMargin)
|
||||
throws IOException {
|
||||
|
||||
ensureStreamIsOpen();
|
||||
|
||||
if (isEndOfPage(freeSpaceForPageBreak)) {
|
||||
this.tableContentStream.close();
|
||||
pageBreak();
|
||||
tableStartedAtNewPage = true;
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
// if you don't have title just use the height of maxTextBox in your
|
||||
// "row"
|
||||
yStart -= height;
|
||||
} else {
|
||||
PageContentStreamOptimized articleTitle = createPdPageContentStream();
|
||||
Paragraph paragraph = new Paragraph(title, font, fontSize, tableWidth, HorizontalAlignment.get(alignment),
|
||||
wrappingFunction);
|
||||
paragraph.setDrawDebug(drawDebug);
|
||||
yStart = paragraph.write(articleTitle, margin, yStart);
|
||||
if (paragraph.getHeight() < height) {
|
||||
yStart -= (height - paragraph.getHeight());
|
||||
}
|
||||
|
||||
articleTitle.close();
|
||||
|
||||
if (drawDebug) {
|
||||
// margin
|
||||
PDStreamUtils.rect(tableContentStream, margin, yStart, width, headerBottomMargin, Color.CYAN);
|
||||
}
|
||||
}
|
||||
|
||||
if (drawHeaderMargin) {
|
||||
yStart -= headerBottomMargin;
|
||||
}
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public Row<T> createRow(float height) {
|
||||
Row<T> row = new Row<T>(this, height);
|
||||
row.setLineSpacing(lineSpacing);
|
||||
this.rows.add(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
public Row<T> createRow(List<Cell<T>> cells, float height) {
|
||||
Row<T> row = new Row<T>(this, cells, height);
|
||||
row.setLineSpacing(lineSpacing);
|
||||
this.rows.add(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Draws table
|
||||
* </p>
|
||||
*
|
||||
* @return Y position of the table
|
||||
* @throws IOException if underlying stream has problem being written to.
|
||||
*/
|
||||
public float draw() throws IOException {
|
||||
ensureStreamIsOpen();
|
||||
|
||||
for (Row<T> row : rows) {
|
||||
if (header.contains(row)) {
|
||||
// check if header row height and first data row height can fit
|
||||
// the page
|
||||
// if not draw them on another side
|
||||
if (isEndOfPage(getMinimumHeight())) {
|
||||
pageBreak();
|
||||
tableStartedAtNewPage = true;
|
||||
}
|
||||
}
|
||||
drawRow(row);
|
||||
}
|
||||
|
||||
endTable();
|
||||
return yStart;
|
||||
}
|
||||
|
||||
private void drawRow(Row<T> row) throws IOException {
|
||||
// row.getHeight is currently an extremely expensive function so get the value
|
||||
// once during drawing and reuse it, since it will not change during drawing
|
||||
float rowHeight = row.getHeight();
|
||||
|
||||
// if it is not header row or first row in the table then remove row's
|
||||
// top border
|
||||
if (row != header && row != rows.get(0)) {
|
||||
if (!isEndOfPage(rowHeight)) {
|
||||
row.removeTopBorders();
|
||||
}
|
||||
}
|
||||
|
||||
// draw the bookmark
|
||||
if (row.getBookmark() != null) {
|
||||
PDPageXYZDestination bookmarkDestination = new PDPageXYZDestination();
|
||||
bookmarkDestination.setPage(currentPage);
|
||||
bookmarkDestination.setTop((int) yStart);
|
||||
row.getBookmark().setDestination(bookmarkDestination);
|
||||
this.addBookmark(row.getBookmark());
|
||||
}
|
||||
|
||||
// we want to remove the borders as often as possible
|
||||
removeTopBorders = true;
|
||||
|
||||
// check also if we want all borders removed
|
||||
if (allBordersRemoved()) {
|
||||
row.removeAllBorders();
|
||||
}
|
||||
|
||||
if (isEndOfPage(rowHeight) && !header.contains(row)) {
|
||||
|
||||
// Draw line at bottom of table
|
||||
endTable();
|
||||
|
||||
// insert page break
|
||||
pageBreak();
|
||||
|
||||
// redraw all headers on each currentPage
|
||||
if (!header.isEmpty()) {
|
||||
for (Row<T> headerRow : header) {
|
||||
drawRow(headerRow);
|
||||
}
|
||||
// after you draw all header rows on next page please keep
|
||||
// removing top borders to avoid double border drawing
|
||||
removeTopBorders = true;
|
||||
} else {
|
||||
// after a page break, we have to ensure that top borders get
|
||||
// drawn
|
||||
removeTopBorders = false;
|
||||
}
|
||||
}
|
||||
// if it is first row in the table, we have to draw the top border
|
||||
if (row == rows.get(0)) {
|
||||
removeTopBorders = false;
|
||||
}
|
||||
|
||||
if (removeTopBorders) {
|
||||
row.removeTopBorders();
|
||||
}
|
||||
|
||||
// if it is header row or first row in the table, we have to draw the
|
||||
// top border
|
||||
if (row == rows.get(0)) {
|
||||
removeTopBorders = false;
|
||||
}
|
||||
|
||||
if (removeTopBorders) {
|
||||
row.removeTopBorders();
|
||||
}
|
||||
|
||||
if (drawLines) {
|
||||
drawVerticalLines(row, rowHeight);
|
||||
}
|
||||
|
||||
if (drawContent) {
|
||||
drawCellContent(row, rowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Method to switch between the {@link PageProvider} and the abstract method
|
||||
* {@link Table#createPage()}, preferring the {@link PageProvider}.
|
||||
* </p>
|
||||
* <p>
|
||||
* Will be removed once {@link #createPage()} is removed.
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private T createNewPage() {
|
||||
if (pageProvider != null) {
|
||||
return pageProvider.nextPage();
|
||||
}
|
||||
|
||||
return createPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return new {@link PDPage}
|
||||
* @deprecated Use a {@link PageProvider} instead
|
||||
*/
|
||||
@Deprecated
|
||||
// remove also createNewPage()
|
||||
protected T createPage() {
|
||||
throw new IllegalStateException(
|
||||
"You either have to provide a " + PageProvider.class.getCanonicalName() + " or override this method");
|
||||
}
|
||||
|
||||
private PageContentStreamOptimized createPdPageContentStream() throws IOException {
|
||||
return new PageContentStreamOptimized(
|
||||
new PDPageContentStream(getDocument(), getCurrentPage(),
|
||||
PDPageContentStream.AppendMode.APPEND, true));
|
||||
}
|
||||
|
||||
private void drawCellContent(Row<T> row, float rowHeight) throws IOException {
|
||||
|
||||
// position into first cell (horizontal)
|
||||
float cursorX = margin;
|
||||
float cursorY;
|
||||
|
||||
for (Cell<T> cell : row.getCells()) {
|
||||
// remember horizontal cursor position, so we can advance to the
|
||||
// next cell easily later
|
||||
float cellStartX = cursorX;
|
||||
if (cell instanceof ImageCell) {
|
||||
final ImageCell<T> imageCell = (ImageCell<T>) cell;
|
||||
|
||||
cursorY = yStart - cell.getTopPadding()
|
||||
- (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth());
|
||||
|
||||
// image cell vertical alignment
|
||||
switch (cell.getValign()) {
|
||||
case TOP:
|
||||
break;
|
||||
case MIDDLE:
|
||||
cursorY -= cell.getVerticalFreeSpace() / 2;
|
||||
break;
|
||||
case BOTTOM:
|
||||
cursorY -= cell.getVerticalFreeSpace();
|
||||
break;
|
||||
}
|
||||
|
||||
cursorX += cell.getLeftPadding() + (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth());
|
||||
|
||||
// image cell horizontal alignment
|
||||
switch (cell.getAlign()) {
|
||||
case CENTER:
|
||||
cursorX += cell.getHorizontalFreeSpace() / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorX += cell.getHorizontalFreeSpace();
|
||||
break;
|
||||
}
|
||||
imageCell.getImage().draw(document, tableContentStream, cursorX, cursorY);
|
||||
|
||||
} else if (cell instanceof TableCell) {
|
||||
final TableCell<T> tableCell = (TableCell<T>) cell;
|
||||
|
||||
cursorY = yStart - cell.getTopPadding()
|
||||
- (cell.getTopBorder() != null ? cell.getTopBorder().getWidth() : 0);
|
||||
|
||||
// table cell vertical alignment
|
||||
switch (cell.getValign()) {
|
||||
case TOP:
|
||||
break;
|
||||
case MIDDLE:
|
||||
cursorY -= cell.getVerticalFreeSpace() / 2;
|
||||
break;
|
||||
case BOTTOM:
|
||||
cursorY -= cell.getVerticalFreeSpace();
|
||||
break;
|
||||
}
|
||||
|
||||
cursorX += cell.getLeftPadding() + (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth());
|
||||
tableCell.setXPosition(cursorX);
|
||||
tableCell.setYPosition(cursorY);
|
||||
this.tableContentStream.endText();
|
||||
tableCell.draw(currentPage);
|
||||
} else {
|
||||
// no text without font
|
||||
if (cell.getFont() == null) {
|
||||
throw new IllegalArgumentException("Font is null on Cell=" + cell.getText());
|
||||
}
|
||||
|
||||
if (cell.isTextRotated()) {
|
||||
// debugging mode - drawing (default!) padding of rotated
|
||||
// cells
|
||||
// left
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart,
|
||||
// 5, cell.getHeight(), Color.GREEN);
|
||||
// top
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart,
|
||||
// cell.getWidth(), 5 , Color.GREEN);
|
||||
// bottom
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart -
|
||||
// cell.getHeight(), cell.getWidth(), -5 , Color.GREEN);
|
||||
// right
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX +
|
||||
// cell.getWidth() - 5, yStart, 5, cell.getHeight(),
|
||||
// Color.GREEN);
|
||||
|
||||
cursorY = yStart - cell.getInnerHeight() - cell.getTopPadding()
|
||||
- (cell.getTopBorder() != null ? cell.getTopBorder().getWidth() : 0);
|
||||
|
||||
switch (cell.getAlign()) {
|
||||
case CENTER:
|
||||
cursorY += cell.getVerticalFreeSpace() / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorY += cell.getVerticalFreeSpace();
|
||||
break;
|
||||
}
|
||||
// respect left padding and descend by font height to get
|
||||
// position of the base line
|
||||
cursorX += cell.getLeftPadding()
|
||||
+ (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth())
|
||||
+ FontUtils.getHeight(cell.getFont(), cell.getFontSize())
|
||||
+ FontUtils.getDescent(cell.getFont(), cell.getFontSize());
|
||||
|
||||
switch (cell.getValign()) {
|
||||
case TOP:
|
||||
break;
|
||||
case MIDDLE:
|
||||
cursorX += cell.getHorizontalFreeSpace() / 2;
|
||||
break;
|
||||
case BOTTOM:
|
||||
cursorX += cell.getHorizontalFreeSpace();
|
||||
break;
|
||||
}
|
||||
// make tokenize method just in case
|
||||
cell.getParagraph().getLines();
|
||||
} else {
|
||||
// debugging mode - drawing (default!) padding of rotated
|
||||
// cells
|
||||
// left
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart,
|
||||
// 5, cell.getHeight(), Color.RED);
|
||||
// top
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart,
|
||||
// cell.getWidth(), 5 , Color.RED);
|
||||
// bottom
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX, yStart -
|
||||
// cell.getHeight(), cell.getWidth(), -5 , Color.RED);
|
||||
// right
|
||||
// PDStreamUtils.rect(tableContentStream, cursorX +
|
||||
// cell.getWidth() - 5, yStart, 5, cell.getHeight(),
|
||||
// Color.RED);
|
||||
|
||||
// position at top of current cell descending by font height
|
||||
// - font descent, because we are
|
||||
// positioning the base line here
|
||||
cursorY = yStart - cell.getTopPadding() - FontUtils.getHeight(cell.getFont(), cell.getFontSize())
|
||||
- FontUtils.getDescent(cell.getFont(), cell.getFontSize())
|
||||
- (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth());
|
||||
|
||||
if (drawDebug) {
|
||||
// @formatter:off
|
||||
// top padding
|
||||
PDStreamUtils.rect(tableContentStream, cursorX + (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth()), yStart - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth()), cell.getWidth() - (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth()) - (cell.getRightBorder() == null ? 0 : cell.getRightBorder().getWidth()), cell.getTopPadding(), Color.RED);
|
||||
// bottom padding
|
||||
PDStreamUtils.rect(tableContentStream, cursorX + (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth()), yStart - cell.getHeight() + (cell.getBottomBorder() == null ? 0 : cell.getBottomBorder().getWidth()) + cell.getBottomPadding(), cell.getWidth() - (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth()) - (cell.getRightBorder() == null ? 0 : cell.getRightBorder().getWidth()), cell.getBottomPadding(), Color.RED);
|
||||
// left padding
|
||||
PDStreamUtils.rect(tableContentStream, cursorX + (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth()), yStart - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth()), cell.getLeftPadding(), cell.getHeight() - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth()) - (cell.getBottomBorder() == null ? 0 : cell.getBottomBorder().getWidth()), Color.RED);
|
||||
// right padding
|
||||
PDStreamUtils.rect(tableContentStream, cursorX + cell.getWidth() - (cell.getRightBorder() == null ? 0 : cell.getRightBorder().getWidth()), yStart - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth()), -cell.getRightPadding(), cell.getHeight() - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth()) - (cell.getBottomBorder() == null ? 0 : cell.getBottomBorder().getWidth()), Color.RED);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// respect left padding
|
||||
cursorX += cell.getLeftPadding()
|
||||
+ (cell.getLeftBorder() == null ? 0 : cell.getLeftBorder().getWidth());
|
||||
|
||||
// the widest text does not fill the inner width of the
|
||||
// cell? no
|
||||
// problem, just add it ;)
|
||||
switch (cell.getAlign()) {
|
||||
case CENTER:
|
||||
cursorX += cell.getHorizontalFreeSpace() / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorX += cell.getHorizontalFreeSpace();
|
||||
break;
|
||||
}
|
||||
|
||||
switch (cell.getValign()) {
|
||||
case TOP:
|
||||
break;
|
||||
case MIDDLE:
|
||||
cursorY -= cell.getVerticalFreeSpace() / 2;
|
||||
break;
|
||||
case BOTTOM:
|
||||
cursorY -= cell.getVerticalFreeSpace();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// remember this horizontal position, as it is the anchor for
|
||||
// each
|
||||
// new line
|
||||
float lineStartX = cursorX;
|
||||
float lineStartY = cursorY;
|
||||
|
||||
this.tableContentStream.setNonStrokingColor(cell.getTextColor());
|
||||
|
||||
int italicCounter = 0;
|
||||
int boldCounter = 0;
|
||||
|
||||
this.tableContentStream.setRotated(cell.isTextRotated());
|
||||
|
||||
// print all lines of the cell
|
||||
for (Map.Entry<Integer, List<Token>> entry : cell.getParagraph().getMapLineTokens().entrySet()) {
|
||||
|
||||
// calculate the width of this line
|
||||
float freeSpaceWithinLine = cell.getParagraph().getMaxLineWidth()
|
||||
- cell.getParagraph().getLineWidth(entry.getKey());
|
||||
// TODO: need to implemented rotated text yo!
|
||||
if (cell.isTextRotated()) {
|
||||
cursorY = lineStartY;
|
||||
switch (cell.getAlign()) {
|
||||
case CENTER:
|
||||
cursorY += freeSpaceWithinLine / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorY += freeSpaceWithinLine;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
cursorX = lineStartX;
|
||||
switch (cell.getAlign()) {
|
||||
case CENTER:
|
||||
cursorX += freeSpaceWithinLine / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
// it doesn't matter because X position is always
|
||||
// the same
|
||||
// as row above
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorX += freeSpaceWithinLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// iterate through tokens in current line
|
||||
PDFont currentFont = cell.getParagraph().getFont(false, false);
|
||||
for (Token token : entry.getValue()) {
|
||||
switch (token.getType()) {
|
||||
case OPEN_TAG:
|
||||
if ("b".equals(token.getData())) {
|
||||
boldCounter++;
|
||||
} else if ("i".equals(token.getData())) {
|
||||
italicCounter++;
|
||||
}
|
||||
break;
|
||||
case CLOSE_TAG:
|
||||
if ("b".equals(token.getData())) {
|
||||
boldCounter = Math.max(boldCounter - 1, 0);
|
||||
} else if ("i".equals(token.getData())) {
|
||||
italicCounter = Math.max(italicCounter - 1, 0);
|
||||
}
|
||||
break;
|
||||
case PADDING:
|
||||
cursorX += Float.parseFloat(token.getData());
|
||||
break;
|
||||
case ORDERING:
|
||||
currentFont = cell.getParagraph().getFont(boldCounter > 0, italicCounter > 0);
|
||||
this.tableContentStream.setFont(currentFont, cell.getFontSize());
|
||||
if (cell.isTextRotated()) {
|
||||
tableContentStream.newLineAt(cursorX, cursorY);
|
||||
this.tableContentStream.showText(token.getData());
|
||||
cursorY += token.getWidth(currentFont) / 1000 * cell.getFontSize();
|
||||
} else {
|
||||
this.tableContentStream.newLineAt(cursorX, cursorY);
|
||||
this.tableContentStream.showText(token.getData());
|
||||
cursorX += token.getWidth(currentFont) / 1000 * cell.getFontSize();
|
||||
}
|
||||
break;
|
||||
case BULLET:
|
||||
float widthOfSpace = currentFont.getSpaceWidth();
|
||||
float halfHeight = FontUtils.getHeight(currentFont, cell.getFontSize()) / 2;
|
||||
if (cell.isTextRotated()) {
|
||||
PDStreamUtils.rect(tableContentStream, cursorX + halfHeight, cursorY,
|
||||
token.getWidth(currentFont) / 1000 * cell.getFontSize(),
|
||||
widthOfSpace / 1000 * cell.getFontSize(),
|
||||
cell.getTextColor());
|
||||
// move cursorY for two characters (one for
|
||||
// bullet, one for space after bullet)
|
||||
cursorY += 2 * widthOfSpace / 1000 * cell.getFontSize();
|
||||
} else {
|
||||
PDStreamUtils.rect(tableContentStream, cursorX, cursorY + halfHeight,
|
||||
token.getWidth(currentFont) / 1000 * cell.getFontSize(),
|
||||
widthOfSpace / 1000 * cell.getFontSize(),
|
||||
cell.getTextColor());
|
||||
// move cursorX for two characters (one for
|
||||
// bullet, one for space after bullet)
|
||||
cursorX += 2 * widthOfSpace / 1000 * cell.getFontSize();
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
currentFont = cell.getParagraph().getFont(boldCounter > 0, italicCounter > 0);
|
||||
this.tableContentStream.setFont(currentFont, cell.getFontSize());
|
||||
if (cell.isTextRotated()) {
|
||||
tableContentStream.newLineAt(cursorX, cursorY);
|
||||
this.tableContentStream.showText(token.getData());
|
||||
cursorY += token.getWidth(currentFont) / 1000 * cell.getFontSize();
|
||||
} else {
|
||||
try {
|
||||
this.tableContentStream.newLineAt(cursorX, cursorY);
|
||||
this.tableContentStream.showText(token.getData());
|
||||
cursorX += token.getWidth(currentFont) / 1000 * cell.getFontSize();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cell.isTextRotated()) {
|
||||
cursorX = cursorX + cell.getParagraph().getFontHeight() * cell.getLineSpacing();
|
||||
} else {
|
||||
cursorY = cursorY - cell.getParagraph().getFontHeight() * cell.getLineSpacing();
|
||||
}
|
||||
}
|
||||
}
|
||||
// set cursor to the start of this cell plus its width to advance to
|
||||
// the next cell
|
||||
cursorX = cellStartX + cell.getWidth();
|
||||
}
|
||||
// Set Y position for next row
|
||||
yStart = yStart - rowHeight;
|
||||
|
||||
}
|
||||
|
||||
private void drawVerticalLines(Row<T> row, float rowHeight) throws IOException {
|
||||
float xStart = margin;
|
||||
|
||||
Iterator<Cell<T>> cellIterator = row.getCells().iterator();
|
||||
while (cellIterator.hasNext()) {
|
||||
Cell<T> cell = cellIterator.next();
|
||||
|
||||
float cellWidth = cellIterator.hasNext()
|
||||
? cell.getWidth()
|
||||
: this.width - (xStart - margin);
|
||||
fillCellColor(cell, yStart, xStart, rowHeight, cellWidth);
|
||||
|
||||
drawCellBorders(rowHeight, cell, xStart);
|
||||
|
||||
xStart += cellWidth;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void drawCellBorders(float rowHeight, Cell<T> cell, float xStart) throws IOException {
|
||||
|
||||
float yEnd = yStart - rowHeight;
|
||||
|
||||
// top
|
||||
LineStyle topBorder = cell.getTopBorder();
|
||||
if (topBorder != null) {
|
||||
float y = yStart - topBorder.getWidth() / 2;
|
||||
drawLine(xStart, y, xStart + cell.getWidth(), y, topBorder);
|
||||
}
|
||||
|
||||
// right
|
||||
LineStyle rightBorder = cell.getRightBorder();
|
||||
if (rightBorder != null) {
|
||||
float x = xStart + cell.getWidth() - rightBorder.getWidth() / 2;
|
||||
drawLine(x, yStart - (topBorder == null ? 0 : topBorder.getWidth()), x, yEnd, rightBorder);
|
||||
}
|
||||
|
||||
// bottom
|
||||
LineStyle bottomBorder = cell.getBottomBorder();
|
||||
if (bottomBorder != null) {
|
||||
float y = yEnd + bottomBorder.getWidth() / 2;
|
||||
drawLine(xStart, y, xStart + cell.getWidth() - (rightBorder == null ? 0 : rightBorder.getWidth()), y,
|
||||
bottomBorder);
|
||||
}
|
||||
|
||||
// left
|
||||
LineStyle leftBorder = cell.getLeftBorder();
|
||||
if (leftBorder != null) {
|
||||
float x = xStart + leftBorder.getWidth() / 2;
|
||||
drawLine(x, yStart, x, yEnd + (bottomBorder == null ? 0 : bottomBorder.getWidth()), leftBorder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void drawLine(float xStart, float yStart, float xEnd, float yEnd, LineStyle border) throws IOException {
|
||||
PDStreamUtils.setLineStyles(tableContentStream, border);
|
||||
tableContentStream.moveTo(xStart, yStart);
|
||||
tableContentStream.lineTo(xEnd, yEnd);
|
||||
tableContentStream.stroke();
|
||||
}
|
||||
|
||||
private void fillCellColor(Cell<T> cell, float yStart, float xStart, float rowHeight, float cellWidth)
|
||||
throws IOException {
|
||||
|
||||
if (cell.getFillColor() != null) {
|
||||
this.tableContentStream.setNonStrokingColor(cell.getFillColor());
|
||||
|
||||
// y start is bottom pos
|
||||
yStart = yStart - rowHeight;
|
||||
float height = rowHeight - (cell.getTopBorder() == null ? 0 : cell.getTopBorder().getWidth());
|
||||
|
||||
this.tableContentStream.addRect(xStart, yStart, cellWidth, height);
|
||||
this.tableContentStream.fill();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureStreamIsOpen() throws IOException {
|
||||
if (tableContentStream == null) {
|
||||
tableContentStream = createPdPageContentStream();
|
||||
}
|
||||
}
|
||||
|
||||
private void endTable() throws IOException {
|
||||
this.tableContentStream.close();
|
||||
}
|
||||
|
||||
public T getCurrentPage() {
|
||||
return this.currentPage;
|
||||
}
|
||||
|
||||
private boolean isEndOfPage(float freeSpaceForPageBreak) {
|
||||
float currentY = yStart - freeSpaceForPageBreak;
|
||||
boolean isEndOfPage = currentY <= pageBottomMargin;
|
||||
if (isEndOfPage) {
|
||||
setTableIsBroken(true);
|
||||
}
|
||||
return isEndOfPage;
|
||||
}
|
||||
|
||||
private void pageBreak() throws IOException {
|
||||
tableContentStream.close();
|
||||
this.yStart = yStartNewPage - pageTopMargin;
|
||||
this.currentPage = createNewPage();
|
||||
this.tableContentStream = createPdPageContentStream();
|
||||
}
|
||||
|
||||
private void addBookmark(PDOutlineItem bookmark) {
|
||||
if (bookmarks == null) {
|
||||
bookmarks = new ArrayList<>();
|
||||
}
|
||||
bookmarks.add(bookmark);
|
||||
}
|
||||
|
||||
public List<PDOutlineItem> getBookmarks() {
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
/**
|
||||
* /**
|
||||
*
|
||||
* @param header row that will be set as table's header row
|
||||
* @deprecated Use {@link #addHeaderRow(Row)} instead, as it supports
|
||||
* multiple header rows
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHeader(Row<T> header) {
|
||||
this.header.clear();
|
||||
addHeaderRow(header);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calculate height of all table cells (essentially, table height).
|
||||
* </p>
|
||||
* <p>
|
||||
* IMPORTANT: Doesn't acknowledge possible page break. Use with caution.
|
||||
* </p>
|
||||
*
|
||||
* @return {@link Table}'s height
|
||||
*/
|
||||
public float getHeaderAndDataHeight() {
|
||||
float height = 0;
|
||||
for (Row<T> row : rows) {
|
||||
height += row.getHeight();
|
||||
}
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Calculates minimum table height that needs to be drawn (all header rows +
|
||||
* first data row heights).
|
||||
* </p>
|
||||
*
|
||||
* @return height
|
||||
*/
|
||||
public float getMinimumHeight() {
|
||||
float height = 0.0f;
|
||||
int firstDataRowIndex = 0;
|
||||
if (!header.isEmpty()) {
|
||||
for (Row<T> headerRow : header) {
|
||||
// count all header rows height
|
||||
height += headerRow.getHeight();
|
||||
firstDataRowIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (rows.size() > firstDataRowIndex) {
|
||||
height += rows.get(firstDataRowIndex).getHeight();
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Setting current row as table header row
|
||||
* </p>
|
||||
*
|
||||
* @param row The row that would be added as table's header row
|
||||
*/
|
||||
public void addHeaderRow(Row<T> row) {
|
||||
this.header.add(row);
|
||||
row.setHeaderRow(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Retrieves last table's header row
|
||||
* </p>
|
||||
*
|
||||
* @return header row
|
||||
*/
|
||||
public Row<T> getHeader() {
|
||||
if (header == null) {
|
||||
throw new IllegalArgumentException("Header Row not set on table");
|
||||
}
|
||||
|
||||
return header.get(header.size() - 1);
|
||||
}
|
||||
|
||||
public float getMargin() {
|
||||
return margin;
|
||||
}
|
||||
|
||||
protected void setYStart(float yStart) {
|
||||
this.yStart = yStart;
|
||||
}
|
||||
|
||||
public boolean isDrawDebug() {
|
||||
return drawDebug;
|
||||
}
|
||||
|
||||
public void setDrawDebug(boolean drawDebug) {
|
||||
this.drawDebug = drawDebug;
|
||||
}
|
||||
|
||||
public boolean tableIsBroken() {
|
||||
return tableIsBroken;
|
||||
}
|
||||
|
||||
public void setTableIsBroken(boolean tableIsBroken) {
|
||||
this.tableIsBroken = tableIsBroken;
|
||||
}
|
||||
|
||||
public List<Row<T>> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public boolean tableStartedAtNewPage() {
|
||||
return tableStartedAtNewPage;
|
||||
}
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
public void setLineSpacing(float lineSpacing) {
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
|
||||
public boolean allBordersRemoved() {
|
||||
return removeAllBorders;
|
||||
}
|
||||
|
||||
public void removeAllBorders(boolean removeAllBorders) {
|
||||
this.removeAllBorders = removeAllBorders;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,339 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TableCell<T extends PDPage> extends Cell<T> {
|
||||
|
||||
private final String tableData;
|
||||
private final float width;
|
||||
private float yStart;
|
||||
private float xStart;
|
||||
private float height = 0;
|
||||
private final PDDocument doc;
|
||||
private final PDPage page;
|
||||
private final float marginBetweenElementsY = FontUtils.getHeight(getFont(), getFontSize());
|
||||
private final HorizontalAlignment align;
|
||||
private final VerticalAlignment valign;
|
||||
|
||||
// default FreeSans font
|
||||
// private PDFont font = FontUtils.getDefaultfonts().get("font");
|
||||
// private PDFont fontBold = FontUtils.getDefaultfonts().get("fontBold");
|
||||
private PageContentStreamOptimized tableCellContentStream;
|
||||
|
||||
// page margins
|
||||
private final float pageTopMargin;
|
||||
private final float pageBottomMargin;
|
||||
// default title fonts
|
||||
private final int tableTitleFontSize = 8;
|
||||
|
||||
TableCell(Row<T> row, float width, String tableData, boolean isCalculated, PDDocument document, PDPage page,
|
||||
float yStart, float pageTopMargin, float pageBottomMargin) throws IOException {
|
||||
this(row, width, tableData, isCalculated, document, page, yStart, pageTopMargin, pageBottomMargin,
|
||||
HorizontalAlignment.LEFT, VerticalAlignment.TOP);
|
||||
}
|
||||
|
||||
TableCell(Row<T> row, float width, String tableData, boolean isCalculated, PDDocument document, PDPage page,
|
||||
float yStart, float pageTopMargin, float pageBottomMargin, final HorizontalAlignment align,
|
||||
final VerticalAlignment valign) throws IOException {
|
||||
super(row, width, tableData, isCalculated);
|
||||
this.tableData = tableData;
|
||||
this.width = width * row.getWidth() / 100;
|
||||
this.doc = document;
|
||||
this.page = page;
|
||||
this.yStart = yStart;
|
||||
this.pageTopMargin = pageTopMargin;
|
||||
this.pageBottomMargin = pageBottomMargin;
|
||||
this.align = align;
|
||||
this.valign = valign;
|
||||
fillTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method just fills up the table's with her content for proper table
|
||||
* cell height calculation. Position of the table (x,y) is not relevant
|
||||
* here.
|
||||
* </p>
|
||||
* <p>
|
||||
* NOTE: if entire row is not header row then use bold instead header cell (
|
||||
* {@code
|
||||
*
|
||||
* <th>})
|
||||
* </p>
|
||||
*/
|
||||
public void fillTable() throws IOException {
|
||||
// please consider the cell's paddings
|
||||
float tableWidth = this.width - getLeftPadding() - getRightPadding();
|
||||
tableCellContentStream = new PageContentStreamOptimized(new PDPageContentStream(doc, page,
|
||||
PDPageContentStream.AppendMode.APPEND, true));
|
||||
// check if there is some additional text outside inner table
|
||||
String[] outerTableText = tableData.split("<table");
|
||||
// don't forget to attach splited tag
|
||||
for (int i = 1; i < outerTableText.length; i++) {
|
||||
outerTableText[i] = "<table " + outerTableText[i];
|
||||
}
|
||||
Paragraph outerTextParagraph = null;
|
||||
String caption = "";
|
||||
height = 0;
|
||||
height = (getTopBorder() == null ? 0 : getTopBorder().getWidth()) + getTopPadding();
|
||||
for (String element : outerTableText) {
|
||||
if (element.contains("</table")) {
|
||||
String[] chunks = element.split("</table>");
|
||||
for (String chunkie : chunks) {
|
||||
|
||||
// make paragraph and get tokens
|
||||
outerTextParagraph = new Paragraph(chunkie, getFont(), 8, (int) tableWidth);
|
||||
outerTextParagraph.getLines();
|
||||
height += (outerTextParagraph != null
|
||||
? outerTextParagraph.getHeight() + marginBetweenElementsY : 0);
|
||||
yStart = writeOrCalculateParagraph(outerTextParagraph, true);
|
||||
}
|
||||
} else {
|
||||
// make paragraph and get tokens
|
||||
outerTextParagraph = new Paragraph(element, getFont(), 8, (int) tableWidth);
|
||||
outerTextParagraph.getLines();
|
||||
height += (outerTextParagraph != null ? outerTextParagraph.getHeight() + marginBetweenElementsY
|
||||
: 0);
|
||||
yStart = writeOrCalculateParagraph(outerTextParagraph, true);
|
||||
}
|
||||
}
|
||||
tableCellContentStream.close();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Method provides writing or height calculation of possible outer text
|
||||
* </p>
|
||||
*
|
||||
* @param paragraph Paragraph that needs to be written or whose height needs to be
|
||||
* calculated
|
||||
* @param onlyCalculateHeight if <code>true</code> the given paragraph will not be drawn
|
||||
* just his height will be calculated.
|
||||
* @return Y position after calculating/writing given paragraph
|
||||
*/
|
||||
private float writeOrCalculateParagraph(Paragraph paragraph, boolean onlyCalculateHeight) throws IOException {
|
||||
int boldCounter = 0;
|
||||
int italicCounter = 0;
|
||||
|
||||
if (!onlyCalculateHeight) {
|
||||
tableCellContentStream.setRotated(isTextRotated());
|
||||
}
|
||||
|
||||
// position at top of current cell descending by font height - font
|
||||
// descent, because we are positioning the base line here
|
||||
float cursorY = yStart - getTopPadding() - FontUtils.getHeight(getFont(), getFontSize())
|
||||
- FontUtils.getDescent(getFont(), getFontSize()) - (getTopBorder() == null ? 0 : getTopBorder().getWidth());
|
||||
float cursorX = xStart;
|
||||
|
||||
// loop through tokens
|
||||
for (Map.Entry<Integer, List<Token>> entry : paragraph.getMapLineTokens().entrySet()) {
|
||||
|
||||
// calculate the width of this line
|
||||
float freeSpaceWithinLine = paragraph.getMaxLineWidth() - paragraph.getLineWidth(entry.getKey());
|
||||
if (isTextRotated()) {
|
||||
switch (align) {
|
||||
case CENTER:
|
||||
cursorY += freeSpaceWithinLine / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorY += freeSpaceWithinLine;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (align) {
|
||||
case CENTER:
|
||||
cursorX += freeSpaceWithinLine / 2;
|
||||
break;
|
||||
case LEFT:
|
||||
// it doesn't matter because X position is always the same
|
||||
// as row above
|
||||
break;
|
||||
case RIGHT:
|
||||
cursorX += freeSpaceWithinLine;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// iterate through tokens in current line
|
||||
PDFont currentFont = paragraph.getFont(false, false);
|
||||
for (Token token : entry.getValue()) {
|
||||
switch (token.getType()) {
|
||||
case OPEN_TAG:
|
||||
if ("b".equals(token.getData())) {
|
||||
boldCounter++;
|
||||
} else if ("i".equals(token.getData())) {
|
||||
italicCounter++;
|
||||
}
|
||||
break;
|
||||
case CLOSE_TAG:
|
||||
if ("b".equals(token.getData())) {
|
||||
boldCounter = Math.max(boldCounter - 1, 0);
|
||||
} else if ("i".equals(token.getData())) {
|
||||
italicCounter = Math.max(italicCounter - 1, 0);
|
||||
}
|
||||
break;
|
||||
case PADDING:
|
||||
cursorX += Float.parseFloat(token.getData());
|
||||
break;
|
||||
case ORDERING:
|
||||
currentFont = paragraph.getFont(boldCounter > 0, italicCounter > 0);
|
||||
tableCellContentStream.setFont(currentFont, getFontSize());
|
||||
if (isTextRotated()) {
|
||||
// if it is not calculation then draw it
|
||||
if (!onlyCalculateHeight) {
|
||||
tableCellContentStream.newLineAt(cursorX, cursorY);
|
||||
tableCellContentStream.showText(token.getData());
|
||||
}
|
||||
cursorY += token.getWidth(currentFont) / 1000 * getFontSize();
|
||||
} else {
|
||||
// if it is not calculation then draw it
|
||||
if (!onlyCalculateHeight) {
|
||||
tableCellContentStream.newLineAt(cursorX, cursorY);
|
||||
tableCellContentStream.showText(token.getData());
|
||||
}
|
||||
cursorX += token.getWidth(currentFont) / 1000 * getFontSize();
|
||||
}
|
||||
break;
|
||||
case BULLET:
|
||||
float widthOfSpace = currentFont.getSpaceWidth();
|
||||
float halfHeight = FontUtils.getHeight(currentFont, getFontSize()) / 2;
|
||||
if (isTextRotated()) {
|
||||
if (!onlyCalculateHeight) {
|
||||
PDStreamUtils.rect(tableCellContentStream, cursorX + halfHeight, cursorY,
|
||||
token.getWidth(currentFont) / 1000 * getFontSize(),
|
||||
widthOfSpace / 1000 * getFontSize(), getTextColor());
|
||||
}
|
||||
// move cursorY for two characters (one for bullet, one
|
||||
// for space after bullet)
|
||||
cursorY += 2 * widthOfSpace / 1000 * getFontSize();
|
||||
} else {
|
||||
if (!onlyCalculateHeight) {
|
||||
PDStreamUtils.rect(tableCellContentStream, cursorX, cursorY + halfHeight,
|
||||
token.getWidth(currentFont) / 1000 * getFontSize(),
|
||||
widthOfSpace / 1000 * getFontSize(), getTextColor());
|
||||
}
|
||||
// move cursorX for two characters (one for bullet, one
|
||||
// for space after bullet)
|
||||
cursorX += 2 * widthOfSpace / 1000 * getFontSize();
|
||||
}
|
||||
break;
|
||||
case TEXT:
|
||||
currentFont = paragraph.getFont(boldCounter > 0, italicCounter > 0);
|
||||
tableCellContentStream.setFont(currentFont, getFontSize());
|
||||
if (isTextRotated()) {
|
||||
if (!onlyCalculateHeight) {
|
||||
tableCellContentStream.newLineAt(cursorX, cursorY);
|
||||
tableCellContentStream.showText(token.getData());
|
||||
}
|
||||
cursorY += token.getWidth(currentFont) / 1000 * getFontSize();
|
||||
} else {
|
||||
if (!onlyCalculateHeight) {
|
||||
tableCellContentStream.newLineAt(cursorX, cursorY);
|
||||
tableCellContentStream.showText(token.getData());
|
||||
}
|
||||
cursorX += token.getWidth(currentFont) / 1000 * getFontSize();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// reset
|
||||
cursorX = xStart;
|
||||
cursorY -= FontUtils.getHeight(getFont(), getFontSize());
|
||||
}
|
||||
return cursorY;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method draw table cell with proper X,Y position which are determined
|
||||
* in {@link Table#draw()} method
|
||||
* </p>
|
||||
* <p>
|
||||
* NOTE: if entire row is not header row then use bold instead header cell (
|
||||
* {@code
|
||||
*
|
||||
* <th>})
|
||||
* </p>
|
||||
*
|
||||
* @param page {@link PDPage} where table cell be written on
|
||||
*/
|
||||
public void draw(PDPage page) throws IOException {
|
||||
// please consider the cell's paddings
|
||||
float tableWidth = this.width - getLeftPadding() - getRightPadding();
|
||||
tableCellContentStream = new PageContentStreamOptimized(new PDPageContentStream(doc, page,
|
||||
PDPageContentStream.AppendMode.APPEND, true));
|
||||
// check if there is some additional text outside inner table
|
||||
String[] outerTableText = tableData.split("<table");
|
||||
// don't forget to attach splited tag
|
||||
for (int i = 1; i < outerTableText.length; i++) {
|
||||
outerTableText[i] = "<table " + outerTableText[i];
|
||||
}
|
||||
Paragraph outerTextParagraph = null;
|
||||
String caption = "";
|
||||
height = 0;
|
||||
height = (getTopBorder() == null ? 0 : getTopBorder().getWidth()) + getTopPadding();
|
||||
for (String element : outerTableText) {
|
||||
if (element.contains("</table")) {
|
||||
String[] chunks = element.split("</table>");
|
||||
for (String chunkie : chunks) {
|
||||
|
||||
// make paragraph and get tokens
|
||||
outerTextParagraph = new Paragraph(chunkie, getFont(), 8, (int) tableWidth);
|
||||
outerTextParagraph.getLines();
|
||||
height += (outerTextParagraph != null
|
||||
? outerTextParagraph.getHeight() + marginBetweenElementsY : 0);
|
||||
yStart = writeOrCalculateParagraph(outerTextParagraph, false);
|
||||
}
|
||||
} else {
|
||||
// make paragraph and get tokens
|
||||
outerTextParagraph = new Paragraph(element, getFont(), 8, (int) tableWidth);
|
||||
outerTextParagraph.getLines();
|
||||
height += (outerTextParagraph != null ? outerTextParagraph.getHeight() + marginBetweenElementsY
|
||||
: 0);
|
||||
yStart = writeOrCalculateParagraph(outerTextParagraph, false);
|
||||
}
|
||||
}
|
||||
tableCellContentStream.close();
|
||||
}
|
||||
|
||||
public float getXPosition() {
|
||||
return xStart;
|
||||
}
|
||||
|
||||
public void setXPosition(float xStart) {
|
||||
this.xStart = xStart;
|
||||
}
|
||||
|
||||
public float getYPosition() {
|
||||
return yStart;
|
||||
}
|
||||
|
||||
public void setYPosition(float yStart) {
|
||||
this.yStart = yStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getTextHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHorizontalFreeSpace() {
|
||||
return getInnerWidth() - width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVerticalFreeSpace() {
|
||||
return getInnerHeight() - width;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.io.IOException;
|
||||
|
||||
class TextToken extends Token {
|
||||
|
||||
private PDFont cachedWidthFont;
|
||||
|
||||
private float cachedWidth;
|
||||
|
||||
TextToken(TokenType type, String data) {
|
||||
super(type, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getWidth(PDFont font) throws IOException {
|
||||
if (font == cachedWidthFont) {
|
||||
return cachedWidth;
|
||||
}
|
||||
cachedWidth = super.getWidth(font);
|
||||
cachedWidthFont = font;
|
||||
return cachedWidth;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
public enum TextType {
|
||||
HIGHLIGHT, UNDERLINE, SQUIGGLY, STRIKEOUT
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Token {
|
||||
|
||||
private final TokenType type;
|
||||
|
||||
private final String data;
|
||||
|
||||
public Token(TokenType type, String data) {
|
||||
this.type = type;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public TokenType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public float getWidth(PDFont font) throws IOException {
|
||||
return font.getStringWidth(getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[" + type + "/" + data + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Token token = (Token) o;
|
||||
return getType() == token.getType() &&
|
||||
Objects.equals(getData(), token.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getType(), getData());
|
||||
}
|
||||
|
||||
public static Token text(TokenType type, String data) {
|
||||
return new TextToken(type, data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
public enum TokenType {
|
||||
TEXT, POSSIBLE_WRAP_POINT, WRAP_POINT, OPEN_TAG, CLOSE_TAG, PADDING, BULLET, ORDERING
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public final class Tokenizer {
|
||||
|
||||
private static final Token OPEN_TAG_I = new Token(TokenType.OPEN_TAG, "i");
|
||||
private static final Token OPEN_TAG_B = new Token(TokenType.OPEN_TAG, "b");
|
||||
private static final Token OPEN_TAG_OL = new Token(TokenType.OPEN_TAG, "ol");
|
||||
private static final Token OPEN_TAG_UL = new Token(TokenType.OPEN_TAG, "ul");
|
||||
private static final Token CLOSE_TAG_I = new Token(TokenType.CLOSE_TAG, "i");
|
||||
private static final Token CLOSE_TAG_B = new Token(TokenType.CLOSE_TAG, "b");
|
||||
private static final Token CLOSE_TAG_OL = new Token(TokenType.CLOSE_TAG, "ol");
|
||||
private static final Token CLOSE_TAG_UL = new Token(TokenType.CLOSE_TAG, "ul");
|
||||
private static final Token CLOSE_TAG_P = new Token(TokenType.CLOSE_TAG, "p");
|
||||
private static final Token CLOSE_TAG_LI = new Token(TokenType.CLOSE_TAG, "li");
|
||||
private static final Token POSSIBLE_WRAP_POINT = new Token(TokenType.POSSIBLE_WRAP_POINT, "");
|
||||
private static final Token WRAP_POINT_P = new Token(TokenType.WRAP_POINT, "p");
|
||||
private static final Token WRAP_POINT_LI = new Token(TokenType.WRAP_POINT, "li");
|
||||
private static final Token WRAP_POINT_BR = new Token(TokenType.WRAP_POINT, "br");
|
||||
|
||||
private Tokenizer() {
|
||||
}
|
||||
|
||||
private static boolean isWrapPointChar(char ch) {
|
||||
return
|
||||
ch == ' ' ||
|
||||
ch == ',' ||
|
||||
ch == '.' ||
|
||||
ch == '-' ||
|
||||
ch == '@' ||
|
||||
ch == ':' ||
|
||||
ch == ';' ||
|
||||
ch == '\n' ||
|
||||
ch == '\t' ||
|
||||
ch == '\r' ||
|
||||
ch == '\f' ||
|
||||
ch == '\u000B';
|
||||
}
|
||||
|
||||
private static Stack<Integer> findWrapPoints(String text) {
|
||||
Stack<Integer> result = new Stack<>();
|
||||
result.push(text.length());
|
||||
for (int i = text.length() - 2; i >= 0; i--) {
|
||||
if (isWrapPointChar(text.charAt(i))) {
|
||||
result.push(i + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Stack<Integer> findWrapPointsWithFunction(String text, WrappingFunction wrappingFunction) {
|
||||
final String[] split = wrappingFunction.getLines(text);
|
||||
int textIndex = text.length();
|
||||
final Stack<Integer> possibleWrapPoints = new Stack<>();
|
||||
possibleWrapPoints.push(textIndex);
|
||||
for (int i = split.length - 1; i > 0; i--) {
|
||||
final int splitLength = split[i].length();
|
||||
possibleWrapPoints.push(textIndex - splitLength);
|
||||
textIndex -= splitLength;
|
||||
}
|
||||
return possibleWrapPoints;
|
||||
}
|
||||
|
||||
public static List<Token> tokenize(final String text, final WrappingFunction wrappingFunction) {
|
||||
final List<Token> tokens = new ArrayList<>();
|
||||
if (text != null) {
|
||||
final Stack<Integer> possibleWrapPoints = wrappingFunction == null
|
||||
? findWrapPoints(text)
|
||||
: findWrapPointsWithFunction(text, wrappingFunction);
|
||||
int textIndex = 0;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
// taking first wrap point
|
||||
Integer currentWrapPoint = possibleWrapPoints.pop();
|
||||
while (textIndex < text.length()) {
|
||||
if (textIndex == currentWrapPoint) {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(POSSIBLE_WRAP_POINT);
|
||||
currentWrapPoint = possibleWrapPoints.pop();
|
||||
}
|
||||
final char c = text.charAt(textIndex);
|
||||
if (c == '<') {
|
||||
boolean consumed = false;
|
||||
if (textIndex < text.length() - 2) {
|
||||
final char lookahead1 = text.charAt(textIndex + 1);
|
||||
final char lookahead2 = text.charAt(textIndex + 2);
|
||||
if ('i' == lookahead1 && '>' == lookahead2) {
|
||||
// <i>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(OPEN_TAG_I);
|
||||
textIndex += 2;
|
||||
consumed = true;
|
||||
} else if ('b' == lookahead1 && '>' == lookahead2) {
|
||||
// <b>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(OPEN_TAG_B);
|
||||
textIndex += 2;
|
||||
consumed = true;
|
||||
} else if ('b' == lookahead1 && 'r' == lookahead2) {
|
||||
if (textIndex < text.length() - 3) {
|
||||
// <br>
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
if (lookahead3 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(WRAP_POINT_BR);
|
||||
// normal notation <br>
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
} else if (textIndex < text.length() - 4) {
|
||||
// <br/>
|
||||
final char lookahead4 = text.charAt(textIndex + 4);
|
||||
if (lookahead3 == '/' && lookahead4 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(WRAP_POINT_BR);
|
||||
// normal notation <br/>
|
||||
textIndex += 4;
|
||||
consumed = true;
|
||||
} else if (textIndex < text.length() - 5) {
|
||||
final char lookahead5 = text.charAt(textIndex + 5);
|
||||
if (lookahead3 == ' ' && lookahead4 == '/' && lookahead5 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(WRAP_POINT_BR);
|
||||
// in case it is notation <br />
|
||||
textIndex += 5;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ('p' == lookahead1 && '>' == lookahead2) {
|
||||
// <p>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(WRAP_POINT_P);
|
||||
textIndex += 2;
|
||||
consumed = true;
|
||||
} else if ('o' == lookahead1 && 'l' == lookahead2) {
|
||||
// <ol>
|
||||
if (textIndex < text.length() - 3) {
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
if (lookahead3 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(OPEN_TAG_OL);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
} else if ('u' == lookahead1 && 'l' == lookahead2) {
|
||||
// <ul>
|
||||
if (textIndex < text.length() - 3) {
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
if (lookahead3 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(OPEN_TAG_UL);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
} else if ('l' == lookahead1 && 'i' == lookahead2) {
|
||||
// <li>
|
||||
if (textIndex < text.length() - 3) {
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
if (lookahead3 == '>') {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
// clean string builder
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(WRAP_POINT_LI);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
} else if ('/' == lookahead1) {
|
||||
// one character tags
|
||||
if (textIndex < text.length() - 3) {
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
if ('>' == lookahead3) {
|
||||
if ('i' == lookahead2) {
|
||||
// </i>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_I);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
} else if ('b' == lookahead2) {
|
||||
// </b>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_B);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
} else if ('p' == lookahead2) {
|
||||
//</p>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_P);
|
||||
textIndex += 3;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (textIndex < text.length() - 4) {
|
||||
// lists
|
||||
final char lookahead3 = text.charAt(textIndex + 3);
|
||||
final char lookahead4 = text.charAt(textIndex + 4);
|
||||
if ('l' == lookahead3) {
|
||||
if ('o' == lookahead2 && '>' == lookahead4) {
|
||||
// </ol>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_OL);
|
||||
textIndex += 4;
|
||||
consumed = true;
|
||||
} else if ('u' == lookahead2 && '>' == lookahead4) {
|
||||
// </ul>
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_UL);
|
||||
textIndex += 4;
|
||||
consumed = true;
|
||||
}
|
||||
} else if ('l' == lookahead2 && 'i' == lookahead3) {
|
||||
// </li>
|
||||
if ('>' == lookahead4) {
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(CLOSE_TAG_LI);
|
||||
textIndex += 4;
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!consumed) {
|
||||
sb.append('<');
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
textIndex++;
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
tokens.add(Token.text(TokenType.TEXT, sb.toString()));
|
||||
sb.delete(0, sb.length());
|
||||
}
|
||||
tokens.add(POSSIBLE_WRAP_POINT);
|
||||
return tokens;
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
public enum VerticalAlignment {
|
||||
TOP, MIDDLE, BOTTOM;
|
||||
|
||||
public static VerticalAlignment get(final String key) {
|
||||
switch (key == null ? "top" : key.toLowerCase().trim()) {
|
||||
case "top":
|
||||
return TOP;
|
||||
case "middle":
|
||||
return MIDDLE;
|
||||
case "bottom":
|
||||
return BOTTOM;
|
||||
default:
|
||||
return TOP;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package org.xbib.graphics.pdfbox.layout.boxable;
|
||||
|
||||
public interface WrappingFunction {
|
||||
|
||||
String[] getLines(String text);
|
||||
}
|
|
@ -93,7 +93,7 @@ public class Document implements RenderListener {
|
|||
|
||||
private Entry<Element, LayoutHint> createEntry(final Element element,
|
||||
final LayoutHint layoutHint) {
|
||||
return new SimpleEntry<Element, LayoutHint>(element, layoutHint);
|
||||
return new SimpleEntry<>(element, layoutHint);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,31 +237,20 @@ public class Document implements RenderListener {
|
|||
Element element = entry.getKey();
|
||||
LayoutHint layoutHint = entry.getValue();
|
||||
boolean success = false;
|
||||
|
||||
// first ask custom renderer to render the element
|
||||
Iterator<Renderer> customRendererIterator = customRenderer
|
||||
.iterator();
|
||||
Iterator<Renderer> customRendererIterator = customRenderer.iterator();
|
||||
while (!success && customRendererIterator.hasNext()) {
|
||||
success = customRendererIterator.next().render(renderContext,
|
||||
element, layoutHint);
|
||||
success = customRendererIterator.next().render(renderContext, element, layoutHint);
|
||||
}
|
||||
|
||||
// if none of them felt responsible, let the default renderer do the job.
|
||||
if (!success) {
|
||||
success = renderContext.render(renderContext, element,
|
||||
layoutHint);
|
||||
success = renderContext.render(renderContext, element, layoutHint);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"neither layout %s nor the render context knows what to do with %s",
|
||||
renderContext.getLayout(), element));
|
||||
|
||||
}
|
||||
}
|
||||
renderContext.close();
|
||||
|
||||
resetPDDocument();
|
||||
return document;
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import org.xbib.graphics.pdfbox.layout.text.Position;
|
|||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* The frame is a container for a {@link Drawable}, that allows to add margin,
|
||||
|
@ -23,7 +23,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||
*/
|
||||
public class Frame implements Element, Drawable, WidthRespecting, Dividable {
|
||||
|
||||
private final List<Drawable> innerList = new CopyOnWriteArrayList<>();
|
||||
private final List<Drawable> innerList = new ArrayList<>();
|
||||
|
||||
private float paddingLeft;
|
||||
private float paddingRight;
|
||||
|
|
|
@ -8,7 +8,6 @@ import org.xbib.graphics.pdfbox.layout.text.Position;
|
|||
import org.xbib.graphics.pdfbox.layout.text.TextFlow;
|
||||
import org.xbib.graphics.pdfbox.layout.text.TextSequenceUtil;
|
||||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A paragraph is used as a container for {@link TextFlow text} that is drawn as
|
||||
|
@ -54,23 +53,22 @@ public class Paragraph extends TextFlow implements Drawable, Element, WidthRespe
|
|||
|
||||
@Override
|
||||
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||
Position upperLeft, DrawListener drawListener) throws IOException {
|
||||
Position upperLeft, DrawListener drawListener) {
|
||||
drawText(contentStream, upperLeft, getAlignment(), drawListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Divided divide(float remainingHeight, final float pageHeight)
|
||||
throws IOException {
|
||||
public Divided divide(float remainingHeight, final float pageHeight) {
|
||||
return TextSequenceUtil.divide(this, getMaxWidth(), remainingHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paragraph removeLeadingEmptyVerticalSpace() throws IOException {
|
||||
public Paragraph removeLeadingEmptyVerticalSpace() {
|
||||
return removeLeadingEmptyLines();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Paragraph removeLeadingEmptyLines() throws IOException {
|
||||
public Paragraph removeLeadingEmptyLines() {
|
||||
Paragraph result = (Paragraph) super.removeLeadingEmptyLines();
|
||||
result.setAbsolutePosition(this.getAbsolutePosition());
|
||||
result.setAlignment(this.getAlignment());
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.xbib.graphics.pdfbox.layout.elements.ControlElement;
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Document;
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Element;
|
||||
|
@ -17,7 +18,6 @@ import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
|||
import org.xbib.graphics.pdfbox.layout.text.DrawListener;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationDrawListener;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -204,8 +204,7 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
|||
* @return <code>true</code> if the page is rotated by 90/270 degrees.
|
||||
*/
|
||||
public boolean isPageTilted() {
|
||||
return CompatibilityHelper.getPageRotation(page) == 90
|
||||
|| CompatibilityHelper.getPageRotation(page) == 270;
|
||||
return page.getRotation() == 90 || page.getRotation() == 270;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -374,12 +373,9 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
|||
if (nextPageFormat != null) {
|
||||
setPageFormat(nextPageFormat);
|
||||
}
|
||||
|
||||
this.page = new PDPage(getPageFormat().getMediaBox());
|
||||
this.pdDocument.addPage(page);
|
||||
this.contentStream = CompatibilityHelper
|
||||
.createAppendablePDPageContentStream(pdDocument, page);
|
||||
|
||||
this.contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||
// fix orientation
|
||||
if (getPageOrientation() != getPageFormat().getOrientation()) {
|
||||
if (isPageTilted()) {
|
||||
|
@ -389,7 +385,7 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
|||
}
|
||||
}
|
||||
if (isPageTilted()) {
|
||||
CompatibilityHelper.transform(contentStream, 0, 1, -1, 0, getPageHeight(), 0);
|
||||
contentStream.transform(new Matrix(0, 1, -1, 0, getPageHeight(), 0));
|
||||
}
|
||||
resetPositionToUpperLeft();
|
||||
resetMaxPositionOnPage();
|
||||
|
@ -410,8 +406,7 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
|||
document.afterPage(this);
|
||||
|
||||
if (getPageFormat().getRotation() != 0) {
|
||||
int currentRotation = CompatibilityHelper
|
||||
.getPageRotation(getCurrentPage());
|
||||
int currentRotation = getCurrentPage().getRotation();
|
||||
getCurrentPage().setRotation(
|
||||
currentRotation + getPageFormat().getRotation());
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.xbib.graphics.pdfbox.layout.elements.VerticalSpacer;
|
|||
import org.xbib.graphics.pdfbox.layout.text.Alignment;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
@ -246,17 +245,13 @@ public class VerticalLayout implements Layout {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
contentStream.saveGraphicsState();
|
||||
contentStream.addRect(0, pageFormat.getMarginBottom(), renderContext.getPageWidth(),
|
||||
renderContext.getHeight());
|
||||
CompatibilityHelper.clip(contentStream);
|
||||
|
||||
contentStream.clip();
|
||||
drawable.draw(renderContext.getPdDocument(), contentStream,
|
||||
renderContext.getCurrentPosition().add(offsetX, 0), renderContext);
|
||||
|
||||
contentStream.restoreGraphicsState();
|
||||
|
||||
if (movePosition) {
|
||||
renderContext.movePositionBy(0, -drawable.getHeight());
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
|||
public enum BaseFont implements Font {
|
||||
|
||||
TIMES(PDType1Font.TIMES_ROMAN, PDType1Font.TIMES_BOLD,
|
||||
PDType1Font.TIMES_ITALIC, PDType1Font.TIMES_BOLD_ITALIC), //
|
||||
PDType1Font.TIMES_ITALIC, PDType1Font.TIMES_BOLD_ITALIC),
|
||||
|
||||
COURIER(PDType1Font.COURIER, PDType1Font.COURIER_BOLD,
|
||||
PDType1Font.COURIER_OBLIQUE, PDType1Font.COURIER_BOLD_OBLIQUE), //
|
||||
PDType1Font.COURIER_OBLIQUE, PDType1Font.COURIER_BOLD_OBLIQUE),
|
||||
|
||||
HELVETICA(PDType1Font.HELVETICA, PDType1Font.HELVETICA_BOLD,
|
||||
PDType1Font.HELVETICA_OBLIQUE, PDType1Font.HELVETICA_BOLD_OBLIQUE);
|
||||
|
@ -28,8 +28,7 @@ public enum BaseFont implements Font {
|
|||
|
||||
private final PDFont boldItalicFont;
|
||||
|
||||
BaseFont(PDFont plainFont, PDFont boldFont, PDFont italicFont,
|
||||
PDFont boldItalicFont) {
|
||||
BaseFont(PDFont plainFont, PDFont boldFont, PDFont italicFont, PDFont boldItalicFont) {
|
||||
this.plainFont = plainFont;
|
||||
this.boldFont = boldFont;
|
||||
this.italicFont = italicFont;
|
||||
|
@ -37,7 +36,7 @@ public enum BaseFont implements Font {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PDFont getPlainFont() {
|
||||
public PDFont getRegularFont() {
|
||||
return plainFont;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.apache.pdfbox.pdmodel.font.PDFont;
|
|||
|
||||
public interface Font {
|
||||
|
||||
PDFont getPlainFont();
|
||||
PDFont getRegularFont();
|
||||
|
||||
PDFont getBoldFont();
|
||||
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
package org.xbib.graphics.pdfbox.layout.font;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Container for a Font and size.
|
||||
*/
|
||||
public class FontDescriptor {
|
||||
|
||||
/**
|
||||
* the associated font.
|
||||
*/
|
||||
private final PDFont font;
|
||||
private final Font font;
|
||||
|
||||
/**
|
||||
* the font size.
|
||||
*/
|
||||
private final float size;
|
||||
|
||||
/**
|
||||
* Creates the descriptor the the given font and size.
|
||||
*
|
||||
* @param font the font.
|
||||
* @param size the size.
|
||||
*/
|
||||
public FontDescriptor(final PDFont font, final float size) {
|
||||
this.font = font;
|
||||
this.size = size;
|
||||
private final boolean regular;
|
||||
|
||||
private final boolean bold;
|
||||
|
||||
private final boolean italic;
|
||||
|
||||
public FontDescriptor(Font font, float size) {
|
||||
this(font, size, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the font.
|
||||
*/
|
||||
public PDFont getFont() {
|
||||
public FontDescriptor(Font font, float size, boolean bold, boolean italic) {
|
||||
this.font = font;
|
||||
this.size = size;
|
||||
this.regular = !bold && !italic;
|
||||
this.bold = bold;
|
||||
this.italic = italic;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size.
|
||||
*/
|
||||
public float getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public PDFont getSelectedFont() {
|
||||
if (regular) {
|
||||
return font.getRegularFont();
|
||||
}
|
||||
if (italic) {
|
||||
return bold ? font.getBoldItalicFont() : font.getItalicFont();
|
||||
}
|
||||
if (bold) {
|
||||
return font.getBoldFont();
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FontDescriptor [font=" + font + ", size=" + size + "]";
|
||||
|
@ -49,33 +58,14 @@ public class FontDescriptor {
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((font == null) ? 0 : font.hashCode());
|
||||
result = prime * result + Float.floatToIntBits(size);
|
||||
return result;
|
||||
return Objects.hash(font, size, regular, bold, italic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
if (!(obj instanceof FontDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
return Objects.hashCode(obj) == Objects.hash(this);
|
||||
}
|
||||
final FontDescriptor other = (FontDescriptor) obj;
|
||||
if (font == null) {
|
||||
if (other.font != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!font.equals(other.font)) {
|
||||
return false;
|
||||
}
|
||||
return Float.floatToIntBits(size) == Float.floatToIntBits(other.size);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class NotoSansFont implements Font {
|
||||
|
||||
private final PDDocument document;
|
||||
|
||||
private static PDType0Font regular;
|
||||
|
||||
private static PDType0Font bold;
|
||||
|
@ -16,32 +19,47 @@ public class NotoSansFont implements Font {
|
|||
|
||||
private static PDType0Font bolditalic;
|
||||
|
||||
public NotoSansFont(PDDocument document) throws IOException {
|
||||
if (regular == null) {
|
||||
regular = PDType0Font.load(document, Objects.requireNonNull(getClass().getResourceAsStream("NotoSans-Regular.ttf")));
|
||||
bold = PDType0Font.load(document, Objects.requireNonNull(getClass().getResourceAsStream("NotoSans-Bold.ttf")));
|
||||
italic = PDType0Font.load(document, Objects.requireNonNull(getClass().getResourceAsStream("NotoSans-Italic.ttf")));
|
||||
bolditalic = PDType0Font.load(document, Objects.requireNonNull(getClass().getResourceAsStream("NotoSans-BoldItalic.ttf")));
|
||||
}
|
||||
public NotoSansFont(PDDocument document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDFont getPlainFont() {
|
||||
public PDFont getRegularFont() {
|
||||
if (regular == null) {
|
||||
regular = load(document, "NotoSans-Regular.ttf");
|
||||
}
|
||||
return regular;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDFont getBoldFont() {
|
||||
if (bold == null) {
|
||||
bold = load(document, "NotoSans-Bold.ttf");
|
||||
}
|
||||
return bold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDFont getItalicFont() {
|
||||
if (italic == null) {
|
||||
italic = load(document, "NotoSans-Italic.ttf");
|
||||
}
|
||||
return italic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDFont getBoldItalicFont() {
|
||||
if (bolditalic == null) {
|
||||
bolditalic = load(document, "NotoSans-BoldItalic.ttf");
|
||||
}
|
||||
return bolditalic;
|
||||
}
|
||||
|
||||
private static PDType0Font load(PDDocument document, String resourceName) {
|
||||
try {
|
||||
return PDType0Font.load(document, Objects.requireNonNull(NotoSansFont.class.getResourceAsStream(resourceName)));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,16 +4,13 @@ import org.apache.pdfbox.pdmodel.PDDocument;
|
|||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.text.DrawListener;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Abstract base class for shapes which performs the
|
||||
* {@link #fill(PDDocument, PDPageContentStream, Position, float, float, Color, DrawListener)}
|
||||
* and (@link
|
||||
* {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener)}
|
||||
* .
|
||||
* and {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener)}
|
||||
*/
|
||||
public abstract class AbstractShape implements Shape {
|
||||
|
||||
|
@ -21,9 +18,7 @@ public abstract class AbstractShape implements Shape {
|
|||
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||
Position upperLeft, float width, float height, Color color,
|
||||
Stroke stroke, DrawListener drawListener) throws IOException {
|
||||
|
||||
add(pdDocument, contentStream, upperLeft, width, height);
|
||||
|
||||
if (stroke != null) {
|
||||
stroke.applyTo(contentStream);
|
||||
}
|
||||
|
@ -31,29 +26,22 @@ public abstract class AbstractShape implements Shape {
|
|||
contentStream.setStrokingColor(color);
|
||||
}
|
||||
contentStream.stroke();
|
||||
|
||||
if (drawListener != null) {
|
||||
drawListener.drawn(this, upperLeft, width, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fill(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||
Position upperLeft, float width, float height, Color color,
|
||||
DrawListener drawListener) throws IOException {
|
||||
|
||||
add(pdDocument, contentStream, upperLeft, width, height);
|
||||
|
||||
if (color != null) {
|
||||
contentStream.setNonStrokingColor(color);
|
||||
}
|
||||
CompatibilityHelper.fillNonZero(contentStream);
|
||||
|
||||
contentStream.fill();
|
||||
if (drawListener != null) {
|
||||
drawListener.drawn(this, upperLeft, width, height);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.xbib.graphics.pdfbox.layout.shape;
|
|||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
|
@ -14,6 +13,7 @@ public class RoundRect extends AbstractShape {
|
|||
private final static float BEZ = 0.551915024494f;
|
||||
|
||||
private final float cornerRadiusX;
|
||||
|
||||
private final float cornerRadiusY;
|
||||
|
||||
/**
|
||||
|
@ -96,18 +96,16 @@ public class RoundRect extends AbstractShape {
|
|||
|
||||
contentStream.moveTo(a.getX(), a.getY());
|
||||
addLine(contentStream, a.getX(), a.getY(), b.getX(), b.getY());
|
||||
CompatibilityHelper.curveTo(contentStream, b.getX() + bezX, b.getY(), c.getX(),
|
||||
contentStream.curveTo(b.getX() + bezX, b.getY(), c.getX(),
|
||||
c.getY() + bezY, c.getX(), c.getY());
|
||||
// contentStream.addLine(c.getX(), c.getY(), d.getX(), d.getY());
|
||||
addLine(contentStream, c.getX(), c.getY(), d.getX(), d.getY());
|
||||
CompatibilityHelper.curveTo(contentStream, d.getX(), d.getY() - bezY, e.getX() + bezX,
|
||||
contentStream.curveTo(d.getX(), d.getY() - bezY, e.getX() + bezX,
|
||||
e.getY(), e.getX(), e.getY());
|
||||
// contentStream.addLine(e.getX(), e.getY(), f.getX(), f.getY());
|
||||
addLine(contentStream, e.getX(), e.getY(), f.getX(), f.getY());
|
||||
CompatibilityHelper.curveTo(contentStream, f.getX() - bezX, f.getY(), g.getX(),
|
||||
contentStream.curveTo(f.getX() - bezX, f.getY(), g.getX(),
|
||||
g.getY() - bezY, g.getX(), g.getY());
|
||||
addLine(contentStream, g.getX(), g.getY(), h.getX(), h.getY());
|
||||
CompatibilityHelper.curveTo(contentStream, h.getX(), h.getY() + bezY, a.getX() - bezX,
|
||||
contentStream.curveTo(h.getX(), h.getY() + bezY, a.getX() - bezX,
|
||||
a.getY(), a.getX(), a.getY());
|
||||
}
|
||||
|
||||
|
@ -120,7 +118,7 @@ public class RoundRect extends AbstractShape {
|
|||
float y1, float x2, float y2) throws IOException {
|
||||
float xMid = (x1 + x2) / 2f;
|
||||
float yMid = (y1 + y2) / 2f;
|
||||
CompatibilityHelper.curveTo1(contentStream, xMid, yMid, x2, y2);
|
||||
contentStream.curveTo1(xMid, yMid, x2, y2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package org.xbib.graphics.pdfbox.layout.shape;
|
|||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* This is a container for all information needed to perform a stroke.
|
||||
|
@ -51,6 +53,7 @@ public class Stroke {
|
|||
public static class DashPattern {
|
||||
|
||||
private final float[] pattern;
|
||||
|
||||
private final float phase;
|
||||
|
||||
/**
|
||||
|
@ -106,8 +109,11 @@ public class Stroke {
|
|||
}
|
||||
|
||||
private final CapStyle capStyle;
|
||||
|
||||
private final JoinStyle joinStyle;
|
||||
|
||||
private final DashPattern dashPattern;
|
||||
|
||||
private final float lineWidth;
|
||||
|
||||
/**
|
||||
|
@ -168,18 +174,20 @@ public class Stroke {
|
|||
* @param contentStream the content stream to apply this stroke to.
|
||||
* @throws IOException by PDFBox.
|
||||
*/
|
||||
public void applyTo(final PDPageContentStream contentStream)
|
||||
throws IOException {
|
||||
public void applyTo(PDPageContentStream contentStream) throws IOException {
|
||||
if (getCapStyle() != null) {
|
||||
Logger.getLogger("").info(" cap style = " + getCapStyle().value());
|
||||
contentStream.setLineCapStyle(getCapStyle().value());
|
||||
}
|
||||
if (getJoinStyle() != null) {
|
||||
Logger.getLogger("").info(" join style = " + getJoinStyle().value());
|
||||
contentStream.setLineJoinStyle(getJoinStyle().value());
|
||||
}
|
||||
if (getDashPattern() != null) {
|
||||
contentStream.setLineDashPattern(getDashPattern().getPattern(),
|
||||
getDashPattern().getPhase());
|
||||
Logger.getLogger("").info(" dash pattern = " + Arrays.asList(getDashPattern().getPattern()));
|
||||
contentStream.setLineDashPattern(getDashPattern().getPattern(), getDashPattern().getPhase());
|
||||
}
|
||||
Logger.getLogger("").info(" line width = " + getLineWidth());
|
||||
contentStream.setLineWidth(getLineWidth());
|
||||
}
|
||||
|
||||
|
@ -196,10 +204,14 @@ public class Stroke {
|
|||
* A builder providing a fluent interface for creating a stroke.
|
||||
*/
|
||||
public static class StrokeBuilder {
|
||||
|
||||
private CapStyle capStyle = CapStyle.Cap;
|
||||
|
||||
private JoinStyle joinStyle = JoinStyle.Miter;
|
||||
|
||||
private DashPattern dashPattern;
|
||||
float lineWidth = 1f;
|
||||
|
||||
private float lineWidth = 1f;
|
||||
|
||||
public StrokeBuilder capStyle(CapStyle capStyle) {
|
||||
this.capStyle = capStyle;
|
||||
|
|
|
@ -0,0 +1,329 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.AbstractCellRenderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import java.awt.Color;
|
||||
|
||||
public abstract class AbstractCell {
|
||||
|
||||
private static final float DEFAULT_MIN_HEIGHT = 10f;
|
||||
|
||||
private int colSpan = 1;
|
||||
|
||||
private int rowSpan = 1;
|
||||
|
||||
protected AbstractCellRenderer<AbstractCell> drawer;
|
||||
|
||||
private Row row;
|
||||
|
||||
private Column column;
|
||||
|
||||
private float width;
|
||||
|
||||
private float minHeight = DEFAULT_MIN_HEIGHT;
|
||||
|
||||
protected Settings settings;
|
||||
|
||||
public void setColSpan(int colSpan) {
|
||||
this.colSpan = colSpan;
|
||||
}
|
||||
|
||||
public void setRowSpan(int rowSpan) {
|
||||
this.rowSpan = rowSpan;
|
||||
}
|
||||
|
||||
public float getPaddingBottom() {
|
||||
return settings.getPaddingBottom();
|
||||
}
|
||||
|
||||
public float getPaddingTop() {
|
||||
return settings.getPaddingTop();
|
||||
}
|
||||
|
||||
public float getPaddingLeft() {
|
||||
return settings.getPaddingLeft();
|
||||
}
|
||||
|
||||
public float getPaddingRight() {
|
||||
return settings.getPaddingRight();
|
||||
}
|
||||
|
||||
public float getHorizontalPadding() {
|
||||
return settings.getPaddingLeft() + settings.getPaddingRight();
|
||||
}
|
||||
|
||||
public float getVerticalPadding() {
|
||||
return settings.getPaddingTop() + settings.getPaddingBottom();
|
||||
}
|
||||
|
||||
public float getBorderWidthTop() {
|
||||
return hasBorderTop() ? settings.getBorderWidthTop() : 0;
|
||||
}
|
||||
|
||||
public boolean hasBorderTop() {
|
||||
return settings.getBorderWidthTop() != null && settings.getBorderWidthTop() > 0;
|
||||
}
|
||||
|
||||
public float getBorderWidthBottom() {
|
||||
return hasBorderBottom() ? settings.getBorderWidthBottom() : 0;
|
||||
}
|
||||
|
||||
public boolean hasBorderBottom() {
|
||||
return settings.getBorderWidthBottom() != null && settings.getBorderWidthBottom() > 0;
|
||||
}
|
||||
|
||||
public float getBorderWidthLeft() {
|
||||
return hasBorderLeft() ? settings.getBorderWidthLeft() : 0;
|
||||
}
|
||||
|
||||
public boolean hasBorderLeft() {
|
||||
return settings.getBorderWidthLeft() != null && settings.getBorderWidthLeft() > 0;
|
||||
}
|
||||
|
||||
public float getBorderWidthRight() {
|
||||
return hasBorderRight() ? settings.getBorderWidthRight() : 0;
|
||||
}
|
||||
|
||||
public boolean hasBorderRight() {
|
||||
return settings.getBorderWidthRight() != null && settings.getBorderWidthRight() > 0;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleTop() {
|
||||
return settings.getBorderStyleTop();
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleBottom() {
|
||||
return settings.getBorderStyleBottom();
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleLeft() {
|
||||
return settings.getBorderStyleLeft();
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleRight() {
|
||||
return settings.getBorderStyleRight();
|
||||
}
|
||||
|
||||
public boolean hasBackgroundColor() {
|
||||
return settings.getBackgroundColor() != null;
|
||||
}
|
||||
|
||||
public Color getBackgroundColor() {
|
||||
return settings.getBackgroundColor();
|
||||
}
|
||||
|
||||
public Color getBorderColor() {
|
||||
return settings.getBorderColor();
|
||||
}
|
||||
|
||||
public boolean isWordBreak() {
|
||||
return settings.isWordBreak();
|
||||
}
|
||||
|
||||
public Column getColumn() {
|
||||
return column;
|
||||
}
|
||||
|
||||
public static float getDefaultMinHeight() {
|
||||
return DEFAULT_MIN_HEIGHT;
|
||||
}
|
||||
|
||||
public float getMinHeight() {
|
||||
return minHeight;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getColSpan() {
|
||||
return colSpan;
|
||||
}
|
||||
|
||||
public int getRowSpan() {
|
||||
return rowSpan;
|
||||
}
|
||||
|
||||
public Row getRow() {
|
||||
return row;
|
||||
}
|
||||
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void setColumn(Column column) {
|
||||
this.column = column;
|
||||
}
|
||||
|
||||
public void setDrawer(AbstractCellRenderer<AbstractCell> drawer) {
|
||||
this.drawer = drawer;
|
||||
}
|
||||
|
||||
public void setMinHeight(float minHeight) {
|
||||
this.minHeight = minHeight;
|
||||
}
|
||||
|
||||
public void setRow(Row row) {
|
||||
this.row = row;
|
||||
}
|
||||
|
||||
public void setSettings(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
assertIsRendered();
|
||||
return getRowSpan() > 1 ? calculateHeightForRowSpan() : getMinHeight();
|
||||
}
|
||||
|
||||
public Renderer getDrawer() {
|
||||
return this.drawer != null ? this.drawer.withCell(this) : createDefaultDrawer();
|
||||
}
|
||||
|
||||
protected abstract Renderer createDefaultDrawer();
|
||||
|
||||
public float calculateHeightForRowSpan() {
|
||||
Row currentRow = row;
|
||||
float result = currentRow.getHeight();
|
||||
for (int i = 1; i < getRowSpan(); i++) {
|
||||
result += currentRow.getNext().getHeight();
|
||||
currentRow = currentRow.getNext();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void assertIsRendered() {
|
||||
if (column == null || row == null) {
|
||||
throw new TableNotYetBuiltException();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isHorizontallyAligned(HorizontalAlignment alignment) {
|
||||
return getSettings().getHorizontalAlignment() == alignment;
|
||||
}
|
||||
|
||||
public boolean isVerticallyAligned(VerticalAlignment alignment) {
|
||||
return getSettings().getVerticalAlignment() == alignment;
|
||||
}
|
||||
|
||||
/*public abstract static class AbstractCellBuilder<C extends AbstractCell, B extends AbstractCell.AbstractCellBuilder<C, B>> {
|
||||
|
||||
protected Settings settings = new Settings();
|
||||
|
||||
// We don't want to expose settings directly!
|
||||
private void settings(Settings settings) {}
|
||||
|
||||
public B borderWidth(final float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderWidthTop(final float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderWidthBottom(final float borderWidth) {
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderWidthLeft(final float borderWidth) {
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderWidthRight(final float borderWidth) {
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderStyleTop(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleTop(style);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderStyleBottom(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleBottom(style);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderStyleLeft(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleLeft(style);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderStyleRight(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleRight(style);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderStyle(final BorderStyleInterface style) {
|
||||
return this.borderStyleLeft(style)
|
||||
.borderStyleRight(style)
|
||||
.borderStyleBottom(style)
|
||||
.borderStyleTop(style);
|
||||
}
|
||||
|
||||
public B padding(final float padding) {
|
||||
return this.paddingTop(padding)
|
||||
.paddingBottom(padding)
|
||||
.paddingLeft(padding)
|
||||
.paddingRight(padding);
|
||||
}
|
||||
|
||||
public B paddingTop(final float padding) {
|
||||
settings.setPaddingTop(padding);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B paddingBottom(final float padding) {
|
||||
settings.setPaddingBottom(padding);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B paddingLeft(final float padding) {
|
||||
settings.setPaddingLeft(padding);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B paddingRight(final float padding) {
|
||||
settings.setPaddingRight(padding);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B horizontalAlignment(final HorizontalAlignment alignment) {
|
||||
settings.setHorizontalAlignment(alignment);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B verticalAlignment(final VerticalAlignment alignment) {
|
||||
settings.setVerticalAlignment(alignment);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B backgroundColor(final Color backgroundColor) {
|
||||
settings.setBackgroundColor(backgroundColor);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B borderColor(final Color borderColor) {
|
||||
settings.setBorderColor(borderColor);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B wordBreak(final Boolean wordBreak) {
|
||||
settings.setWordBreak(wordBreak);
|
||||
return (B) this;
|
||||
}
|
||||
}*/
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractTextCell extends AbstractCell {
|
||||
|
||||
protected float lineSpacing = 1f;
|
||||
|
||||
public Font getFont() {
|
||||
return settings.getFont();
|
||||
}
|
||||
|
||||
public Integer getFontSize() {
|
||||
return settings.getFontSize();
|
||||
}
|
||||
|
||||
public Color getTextColor() {
|
||||
return settings.getTextColor();
|
||||
}
|
||||
|
||||
private Float textHeight;
|
||||
|
||||
public abstract String getText();
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinHeight() {
|
||||
return Math.max((getVerticalPadding() + getTextHeight()), super.getMinHeight());
|
||||
}
|
||||
|
||||
public float getTextHeight() {
|
||||
if (this.textHeight != null) {
|
||||
return this.textHeight;
|
||||
}
|
||||
this.textHeight = PdfUtil.getFontHeight(getFont(), getFontSize());
|
||||
if (settings.isWordBreak()) {
|
||||
final int size = PdfUtil.getOptimalTextBreakLines(getText(), getFont(), getFontSize(), getMaxWidth()).size();
|
||||
final float heightOfTextLines = size * this.textHeight;
|
||||
final float heightOfLineSpacing = (size - 1) * this.textHeight * getLineSpacing();
|
||||
this.textHeight = heightOfTextLines + heightOfLineSpacing;
|
||||
}
|
||||
return this.textHeight;
|
||||
}
|
||||
|
||||
public float getWidthOfText() {
|
||||
assertIsRendered();
|
||||
final float notBrokenTextWidth = PdfUtil.getStringWidth(getText(), getFont(), getFontSize());
|
||||
if (settings.isWordBreak()) {
|
||||
final float maxWidth = getMaxWidthOfText() - getHorizontalPadding();
|
||||
List<String> textLines = PdfUtil.getOptimalTextBreakLines(getText(), getFont(), getFontSize(), maxWidth);
|
||||
return textLines.stream()
|
||||
.map(line -> PdfUtil.getStringWidth(line, getFont(), getFontSize()))
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(notBrokenTextWidth);
|
||||
}
|
||||
return notBrokenTextWidth;
|
||||
}
|
||||
|
||||
private float getMaxWidthOfText() {
|
||||
float columnsWidth = getColumn().getWidth();
|
||||
if (getColSpan() > 1) {
|
||||
Column currentColumn = getColumn();
|
||||
for (int i = 1; i < getColSpan(); i++) {
|
||||
columnsWidth += currentColumn.getNext().getWidth();
|
||||
currentColumn = currentColumn.getNext();
|
||||
}
|
||||
}
|
||||
return columnsWidth;
|
||||
}
|
||||
|
||||
public float getMaxWidth() {
|
||||
return getMaxWidthOfText() - getHorizontalPadding();
|
||||
}
|
||||
|
||||
/*public abstract static class AbstractTextCellBuilder<C extends AbstractTextCell, B extends AbstractTextCell.AbstractTextCellBuilder<C, B>> extends AbstractCellBuilder<C, B> {
|
||||
|
||||
public B font(final Font font) {
|
||||
settings.setFont(font);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B fontSize(final Integer fontSize) {
|
||||
settings.setFontSize(fontSize);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B textColor(final Color textColor) {
|
||||
settings.setTextColor(textColor);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
}*/
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
public enum BorderStyle implements BorderStyleInterface {
|
||||
|
||||
SOLID(new float[]{}, 0),
|
||||
DOTTED(new float[]{1}, 1),
|
||||
DASHED(new float[]{5,2}, 1);
|
||||
|
||||
private final float[] pattern;
|
||||
private final int phase;
|
||||
|
||||
BorderStyle(float[] pattern, int phase) {
|
||||
this.pattern = pattern;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public float[] getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
public int getPhase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
public interface BorderStyleInterface {
|
||||
|
||||
float[] getPattern();
|
||||
|
||||
int getPhase();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
public class Column {
|
||||
|
||||
private Table table;
|
||||
|
||||
private Column next;
|
||||
|
||||
private float width;
|
||||
|
||||
Column(final float width) {
|
||||
if (width < 0) {
|
||||
throw new IllegalArgumentException("Column width must be non-negative");
|
||||
}
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
public void setTable(Table table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public void setNext(Column next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public Table getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public Column getNext() {
|
||||
return next;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class CouldNotDetermineStringWidthException extends RuntimeException {
|
||||
|
||||
public CouldNotDetermineStringWidthException() {
|
||||
super();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
public enum HorizontalAlignment {
|
||||
|
||||
LEFT, CENTER, RIGHT, JUSTIFY
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotatedStyledText;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Collections;
|
||||
|
||||
public class Hyperlink implements ParagraphProcessor {
|
||||
|
||||
private String text;
|
||||
|
||||
private String url;
|
||||
|
||||
private Font font;
|
||||
|
||||
private Float fontSize;
|
||||
|
||||
private Color color = Color.BLUE;
|
||||
|
||||
private float baselineOffset = 1f;
|
||||
|
||||
public void setFontSize(Float fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setBaselineOffset(float baselineOffset) {
|
||||
this.baselineOffset = baselineOffset;
|
||||
}
|
||||
|
||||
public float getBaselineOffset() {
|
||||
return baselineOffset;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Paragraph paragraph, Settings settings) {
|
||||
Annotations.HyperlinkAnnotation hyperlink =
|
||||
new Annotations.HyperlinkAnnotation(getUrl(), Annotations.HyperlinkAnnotation.LinkStyle.ul);
|
||||
FontDescriptor fontDescriptor = new FontDescriptor(getFont() != null ? getFont() : settings.getFont(),
|
||||
getFontSize() != null ? getFontSize() : settings.getFontSize());
|
||||
paragraph.add(new AnnotatedStyledText(getText(), fontDescriptor,
|
||||
getColor(), getBaselineOffset(), 0, 0, Collections.singleton(hyperlink)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.ImageCellRenderer;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class ImageCell extends AbstractCell {
|
||||
|
||||
private float scale = 1.0f;
|
||||
|
||||
private PDImageXObject image;
|
||||
|
||||
private float maxHeight;
|
||||
|
||||
public void setImage(PDImageXObject image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public void setMaxHeight(float maxHeight) {
|
||||
this.maxHeight = maxHeight;
|
||||
}
|
||||
|
||||
public void setScale(float scale) {
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public float getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public PDImageXObject getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinHeight() {
|
||||
return Math.max((getFitSize().y + getVerticalPadding()), super.getMinHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Renderer createDefaultDrawer() {
|
||||
return new ImageCellRenderer(this);
|
||||
}
|
||||
|
||||
public Point2D.Float getFitSize() {
|
||||
final Point2D.Float sizes = new Point2D.Float();
|
||||
float scaledWidth = image.getWidth() * getScale();
|
||||
float scaledHeight = image.getHeight() * getScale();
|
||||
final float resultingWidth = getWidth() - getHorizontalPadding();
|
||||
|
||||
// maybe reduce the image to fit in column
|
||||
if (scaledWidth > resultingWidth) {
|
||||
scaledHeight = (resultingWidth / scaledWidth) * scaledHeight;
|
||||
scaledWidth = resultingWidth;
|
||||
}
|
||||
|
||||
if (maxHeight > 0.0f && scaledHeight > maxHeight) {
|
||||
scaledWidth = (maxHeight / scaledHeight) * scaledWidth;
|
||||
scaledHeight = maxHeight;
|
||||
}
|
||||
|
||||
sizes.x = scaledWidth;
|
||||
sizes.y = scaledHeight;
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private PDImageXObject image;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder image(PDImageXObject image) {
|
||||
this.image = image;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImageCell build() {
|
||||
ImageCell cell = new ImageCell();
|
||||
cell.setImage(image);
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import org.xbib.graphics.pdfbox.layout.font.BaseFont;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Markup implements ParagraphProcessor {
|
||||
|
||||
public enum MarkupSupportedFont {
|
||||
TIMES, COURIER, HELVETICA
|
||||
}
|
||||
|
||||
public static final Map<MarkupSupportedFont, BaseFont> FONT_MAP = new EnumMap<>(Markup.MarkupSupportedFont.class);
|
||||
static {
|
||||
FONT_MAP.put(Markup.MarkupSupportedFont.HELVETICA, BaseFont.HELVETICA);
|
||||
FONT_MAP.put(Markup.MarkupSupportedFont.COURIER, BaseFont.COURIER);
|
||||
FONT_MAP.put(Markup.MarkupSupportedFont.TIMES, BaseFont.TIMES);
|
||||
}
|
||||
|
||||
private String markup;
|
||||
|
||||
private MarkupSupportedFont font;
|
||||
|
||||
private Float fontSize;
|
||||
|
||||
public void setMarkup(String markup) {
|
||||
this.markup = markup;
|
||||
}
|
||||
|
||||
public String getMarkup() {
|
||||
return markup;
|
||||
}
|
||||
|
||||
public void setFont(MarkupSupportedFont font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public MarkupSupportedFont getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setFontSize(Float fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
public Float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Paragraph paragraph, Settings settings) throws IOException {
|
||||
float fontSize = getFontSize() != null ? getFontSize() : settings.getFontSize();
|
||||
paragraph.addMarkup(getMarkup(), fontSize, FONT_MAP.get(getFont()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
|
||||
public class NewLine implements ParagraphProcessor {
|
||||
|
||||
private final Font font;
|
||||
|
||||
private final float fontSize;
|
||||
|
||||
NewLine(Font font, float fontSize) {
|
||||
this.font = font;
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Paragraph paragraph, Settings settings) {
|
||||
paragraph.add(new org.xbib.graphics.pdfbox.layout.text.NewLine(new FontDescriptor(font, fontSize)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.ParagraphCellRenderer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
|
||||
public class ParagraphCell extends AbstractCell {
|
||||
|
||||
protected float lineSpacing = 1f;
|
||||
|
||||
private Paragraph paragraph;
|
||||
|
||||
public void setLineSpacing(float lineSpacing) {
|
||||
this.lineSpacing = lineSpacing;
|
||||
}
|
||||
|
||||
public float getLineSpacing() {
|
||||
return lineSpacing;
|
||||
}
|
||||
|
||||
public void setParagraph(Paragraph paragraph) {
|
||||
this.paragraph = paragraph;
|
||||
}
|
||||
|
||||
public Paragraph getParagraph() {
|
||||
return paragraph;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidth(float width) {
|
||||
super.setWidth(width);
|
||||
while (getParagraph().getWrappedParagraph().removeLast() != null) {
|
||||
}
|
||||
for (ParagraphProcessor p : getParagraph().getProcessables()) {
|
||||
try {
|
||||
p.process(getParagraph().getWrappedParagraph(), getSettings());
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
org.xbib.graphics.pdfbox.layout.elements.Paragraph wrappedParagraph = paragraph.getWrappedParagraph();
|
||||
wrappedParagraph.setLineSpacing(getLineSpacing());
|
||||
wrappedParagraph.setApplyLineSpacingToFirstLine(false);
|
||||
wrappedParagraph.setMaxWidth(width - getHorizontalPadding());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Renderer createDefaultDrawer() {
|
||||
return new ParagraphCellRenderer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinHeight() {
|
||||
float height = paragraph.getWrappedParagraph().getHeight() + getVerticalPadding();
|
||||
return Math.max(height, super.getMinHeight());
|
||||
}
|
||||
|
||||
public static class Paragraph {
|
||||
|
||||
private final List<ParagraphProcessor> processables;
|
||||
|
||||
private final org.xbib.graphics.pdfbox.layout.elements.Paragraph wrappedParagraph =
|
||||
new org.xbib.graphics.pdfbox.layout.elements.Paragraph();
|
||||
|
||||
public Paragraph(List<ParagraphProcessor> processables) {
|
||||
this.processables = processables;
|
||||
}
|
||||
|
||||
public List<ParagraphProcessor> getProcessables() {
|
||||
return processables;
|
||||
}
|
||||
|
||||
public org.xbib.graphics.pdfbox.layout.elements.Paragraph getWrappedParagraph() {
|
||||
return wrappedParagraph;
|
||||
}
|
||||
|
||||
/*public static class ParagraphBuilder {
|
||||
|
||||
private final List<ParagraphProcessable> processables = new LinkedList<>();
|
||||
|
||||
private ParagraphBuilder() {
|
||||
}
|
||||
|
||||
public ParagraphBuilder append(StyledText styledText) {
|
||||
processables.add(styledText);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParagraphBuilder append(Hyperlink hyperlink) {
|
||||
processables.add(hyperlink);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParagraphBuilder append(Markup markup) {
|
||||
processables.add(markup);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ParagraphBuilder appendNewLine(Font font, float fontSize) {
|
||||
processables.add(new NewLine(font, fontSize));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Paragraph build() {
|
||||
return new Paragraph(processables);
|
||||
}
|
||||
}
|
||||
|
||||
public static ParagraphBuilder builder() {
|
||||
return new ParagraphBuilder();
|
||||
}*/
|
||||
}
|
||||
|
||||
/*public abstract static class ParagraphCellBuilder<C extends ParagraphCell, B extends ParagraphCell.ParagraphCellBuilder<C, B>> extends AbstractCellBuilder<C, B> {
|
||||
|
||||
public B font(final Font font) {
|
||||
settings.setFont(font);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B fontSize(final Integer fontSize) {
|
||||
settings.setFontSize(fontSize);
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
public B textColor(final Color textColor) {
|
||||
settings.setTextColor(textColor);
|
||||
return (B) this;
|
||||
}
|
||||
}*/
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import java.io.IOException;
|
||||
|
||||
public interface ParagraphProcessor {
|
||||
|
||||
void process(Paragraph paragraph, Settings settings) throws IOException;
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides some helping functions.
|
||||
*/
|
||||
public final class PdfUtil {
|
||||
|
||||
public static final String NEW_LINE_REGEX = "\\r?\\n";
|
||||
|
||||
/**
|
||||
* Computes the width of a String (in points).
|
||||
*
|
||||
* @param text Text
|
||||
* @param font Font of Text
|
||||
* @param fontSize FontSize of String
|
||||
* @return Width (in points)
|
||||
*/
|
||||
public static float getStringWidth(final String text, final Font font, final int fontSize) {
|
||||
return Arrays.stream(text.split(NEW_LINE_REGEX))
|
||||
.max(Comparator.comparing(String::length))
|
||||
.map(x -> {
|
||||
try {
|
||||
return getWidthOfStringWithoutNewlines(x, font, fontSize);
|
||||
} catch (IOException exception) {
|
||||
return 0f;
|
||||
}
|
||||
})
|
||||
.orElseThrow(CouldNotDetermineStringWidthException::new);
|
||||
}
|
||||
|
||||
private static float getWidthOfStringWithoutNewlines(String text, Font font, int fontSize) throws IOException {
|
||||
|
||||
final List<String> codePointsAsString = text.codePoints()
|
||||
.mapToObj(codePoint -> new String(new int[]{codePoint}, 0, 1))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<Float> widths = new ArrayList<>();
|
||||
|
||||
for (final String codepoint : codePointsAsString) {
|
||||
try {
|
||||
widths.add(font.getRegularFont().getStringWidth(codepoint) * fontSize / 1000F);
|
||||
} catch (final IllegalArgumentException | IOException e) {
|
||||
widths.add(font.getRegularFont().getStringWidth("–") * fontSize / 1000F);
|
||||
}
|
||||
}
|
||||
|
||||
return widths.stream().reduce(0.0f, Float::sum);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the height of a font.
|
||||
*
|
||||
* @param font Font
|
||||
* @param fontSize FontSize
|
||||
* @return Height of font
|
||||
*/
|
||||
public static float getFontHeight(Font font, final int fontSize) {
|
||||
return font.getRegularFont().getFontDescriptor().getCapHeight() * fontSize / 1000F;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a text into multiple lines to prevent a text-overflow.
|
||||
*
|
||||
* @param text Text
|
||||
* @param font Used font
|
||||
* @param fontSize Used fontSize
|
||||
* @param maxWidth Maximal width of resulting text-lines
|
||||
* @return A list of lines, where all are smaller than maxWidth
|
||||
*/
|
||||
public static List<String> getOptimalTextBreakLines(final String text, final Font font, final int fontSize, final float maxWidth) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (final String line : text.split(NEW_LINE_REGEX)) {
|
||||
if (PdfUtil.doesTextLineFit(line, font, fontSize, maxWidth)) {
|
||||
result.add(line);
|
||||
} else {
|
||||
result.addAll(PdfUtil.wrapLine(line, font, fontSize, maxWidth));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<String> wrapLine(final String line, final Font font, final int fontSize, final float maxWidth) {
|
||||
if (PdfUtil.doesTextLineFit(line, font, fontSize, maxWidth)) {
|
||||
return Collections.singletonList(line);
|
||||
}
|
||||
List<String> goodLines = new ArrayList<>();
|
||||
Stack<String> allWords = new Stack<>();
|
||||
Arrays.asList(line.split("(?<=[\\\\. ,-])")).forEach(allWords::push);
|
||||
Collections.reverse(allWords);
|
||||
while (!allWords.empty()) {
|
||||
goodLines.add(buildALine(allWords, font, fontSize, maxWidth));
|
||||
}
|
||||
return goodLines;
|
||||
}
|
||||
|
||||
private static List<String> splitBySize(final String line, final Font font, final int fontSize, final float maxWidth) {
|
||||
List<String> returnList = new ArrayList<>();
|
||||
for (int i = line.length() - 1; i > 0; i--) {
|
||||
String fittedNewLine = line.substring(0, i) + "-";
|
||||
String remains = line.substring(i);
|
||||
if (PdfUtil.doesTextLineFit(fittedNewLine, font, fontSize, maxWidth)) {
|
||||
returnList.add(fittedNewLine);
|
||||
returnList.addAll(PdfUtil.wrapLine(remains, font, fontSize, maxWidth));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
}
|
||||
|
||||
|
||||
private static String buildALine(final Stack<String> words,
|
||||
final Font font,
|
||||
final int fontSize,
|
||||
final float maxWidth) {
|
||||
StringBuilder line = new StringBuilder();
|
||||
float width = 0;
|
||||
while (!words.empty()) {
|
||||
float nextWordWidth = getStringWidth(words.peek(), font, fontSize);
|
||||
if (line.length() == 0 && words.peek().length() == 1 && nextWordWidth > maxWidth) {
|
||||
return words.pop();
|
||||
}
|
||||
if (doesTextLineFit(width + nextWordWidth, maxWidth)) {
|
||||
line.append(words.pop());
|
||||
width += nextWordWidth;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (width == 0 && !words.empty()) {
|
||||
List<String> cutBySize = splitBySize(words.pop(), font, fontSize, maxWidth);
|
||||
Collections.reverse(cutBySize);
|
||||
cutBySize.forEach(words::push);
|
||||
return buildALine(words, font, fontSize, maxWidth);
|
||||
}
|
||||
return line.toString().trim();
|
||||
}
|
||||
|
||||
private static boolean doesTextLineFit(String textLine, Font font, int fontSize, float maxWidth) {
|
||||
return doesTextLineFit(PdfUtil.getStringWidth(textLine, font, fontSize), maxWidth);
|
||||
}
|
||||
|
||||
private static boolean doesTextLineFit(float stringWidth, float maxWidth) {
|
||||
if (isEqualInEpsilon(stringWidth, maxWidth)) {
|
||||
return true;
|
||||
}
|
||||
return maxWidth > stringWidth;
|
||||
}
|
||||
|
||||
private static boolean isEqualInEpsilon(float x, float y) {
|
||||
return Math.abs(y - x) < 0.0001;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class RepeatedHeaderTableRenderer extends TableRenderer {
|
||||
|
||||
private int numberOfRowsToRepeat = 1;
|
||||
|
||||
private Float headerHeight;
|
||||
|
||||
public RepeatedHeaderTableRenderer(Table table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawPage(PageData pageData) {
|
||||
if (pageData.firstRowOnPage != 0) {
|
||||
float adaption = 0;
|
||||
for (int i = 0; i < numberOfRowsToRepeat; i++) {
|
||||
adaption += table.getRows().get(i).getHeight();
|
||||
Point2D.Float startPoint = new Point2D.Float(this.startX,
|
||||
this.startY + calculateHeightForFirstRows() - adaption);
|
||||
drawRow(startPoint, table.getRows().get(i), i, (drawer, drawingContext) -> {
|
||||
drawer.renderBackground(drawingContext);
|
||||
drawer.renderContent(drawingContext);
|
||||
drawer.renderBorders(drawingContext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
drawerList.forEach(drawer -> drawWithFunction(pageData, new Point2D.Float(this.startX, this.startY), drawer));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void determinePageToStartTable(float yOffsetOnNewPage) {
|
||||
float minimumRowsToFitHeight = 0;
|
||||
int minimumRowsToFit = table.getRows().size() > numberOfRowsToRepeat
|
||||
? numberOfRowsToRepeat + 1
|
||||
: numberOfRowsToRepeat;
|
||||
|
||||
for (final Row row : table.getRows().subList(0, minimumRowsToFit))
|
||||
minimumRowsToFitHeight += row.getHeight();
|
||||
|
||||
if (startY - minimumRowsToFitHeight < endY) {
|
||||
startY = yOffsetOnNewPage + calculateHeightForFirstRows();
|
||||
startTableInNewPage = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Supplier<PDDocument> documentSupplier, Supplier<PDPage> pageSupplier, float yOffset)
|
||||
throws IOException {
|
||||
super.draw(documentSupplier, pageSupplier, yOffset + calculateHeightForFirstRows());
|
||||
}
|
||||
|
||||
private float calculateHeightForFirstRows() {
|
||||
if (headerHeight != null) {
|
||||
return headerHeight;
|
||||
}
|
||||
|
||||
float height = 0;
|
||||
for (int i = 0; i < numberOfRowsToRepeat; i++) {
|
||||
height += table.getRows().get(i).getHeight();
|
||||
}
|
||||
|
||||
// Cache and return
|
||||
headerHeight = height;
|
||||
return height;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,180 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Comparator.naturalOrder;
|
||||
|
||||
public class Row {
|
||||
|
||||
private static final Float DEFAULT_HEIGHT = 10f;
|
||||
|
||||
private Table table;
|
||||
|
||||
private List<AbstractCell> cells;
|
||||
|
||||
private Settings settings;
|
||||
|
||||
private Float height;
|
||||
|
||||
private Row next;
|
||||
|
||||
private Row(final List<AbstractCell> cells) {
|
||||
super();
|
||||
this.cells = cells;
|
||||
}
|
||||
|
||||
public void setSettings(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public Row getNext() {
|
||||
return next;
|
||||
}
|
||||
|
||||
public static Float getDefaultHeight() {
|
||||
return DEFAULT_HEIGHT;
|
||||
}
|
||||
|
||||
public List<AbstractCell> getCells() {
|
||||
return cells;
|
||||
}
|
||||
|
||||
public Table getTable() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public void setCells(List<AbstractCell> cells) {
|
||||
this.cells = cells;
|
||||
}
|
||||
|
||||
public void setHeight(Float height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void setNext(Row next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public void setTable(Table table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
if (table == null) {
|
||||
throw new TableNotYetBuiltException();
|
||||
}
|
||||
|
||||
if (height == null) {
|
||||
this.height = getCells().stream()
|
||||
.filter(cell -> cell.getRowSpan() == 1)
|
||||
.map(AbstractCell::getHeight)
|
||||
.max(naturalOrder())
|
||||
.orElse(DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
void doRowSpanSizeAdaption(float heightOfHighestCell, float rowsHeight) {
|
||||
final float rowSpanSizeDifference = heightOfHighestCell - rowsHeight;
|
||||
this.height += (this.height / (heightOfHighestCell - rowSpanSizeDifference)) * rowSpanSizeDifference;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final List<AbstractCell> cells = new ArrayList<>();
|
||||
|
||||
private final Settings settings = new Settings();
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder add(final AbstractCell cell) {
|
||||
cells.add(cell);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder font(Font font) {
|
||||
settings.setFont(font);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fontSize(final Integer fontSize) {
|
||||
settings.setFontSize(fontSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder textColor(final Color textColor) {
|
||||
settings.setTextColor(textColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder backgroundColor(final Color backgroundColor) {
|
||||
settings.setBackgroundColor(backgroundColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder padding(final float padding) {
|
||||
settings.setPaddingTop(padding);
|
||||
settings.setPaddingBottom(padding);
|
||||
settings.setPaddingLeft(padding);
|
||||
settings.setPaddingRight(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidth(final float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyle(final BorderStyleInterface borderStyle) {
|
||||
settings.setBorderStyleTop(borderStyle);
|
||||
settings.setBorderStyleBottom(borderStyle);
|
||||
settings.setBorderStyleLeft(borderStyle);
|
||||
settings.setBorderStyleRight(borderStyle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderColor(final Color borderColor) {
|
||||
settings.setBorderColor(borderColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder horizontalAlignment(HorizontalAlignment alignment) {
|
||||
settings.setHorizontalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder verticalAlignment(VerticalAlignment alignment) {
|
||||
settings.setVerticalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder wordBreak(Boolean wordBreak) {
|
||||
settings.setWordBreak(wordBreak);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Row build() {
|
||||
final Row row = new Row(cells);
|
||||
row.setSettings(settings);
|
||||
//row.setHeight(height);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class RowIsTooHighException extends RuntimeException {
|
||||
|
||||
public RowIsTooHighException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import java.awt.Color;
|
||||
|
||||
public class Settings {
|
||||
|
||||
private Font font;
|
||||
|
||||
private Integer fontSize;
|
||||
|
||||
private Color textColor;
|
||||
|
||||
private Color backgroundColor;
|
||||
|
||||
private Float borderWidthTop;
|
||||
|
||||
private Float borderWidthBottom;
|
||||
|
||||
private Float borderWidthLeft;
|
||||
|
||||
private Float borderWidthRight;
|
||||
|
||||
private Color borderColor;
|
||||
|
||||
private Float paddingLeft;
|
||||
|
||||
private Float paddingRight;
|
||||
|
||||
private Float paddingTop;
|
||||
|
||||
private Float paddingBottom;
|
||||
|
||||
private BorderStyleInterface borderStyleLeft;
|
||||
|
||||
private BorderStyleInterface borderStyleRight;
|
||||
|
||||
private BorderStyleInterface borderStyleTop;
|
||||
|
||||
private BorderStyleInterface borderStyleBottom;
|
||||
|
||||
private HorizontalAlignment horizontalAlignment;
|
||||
|
||||
private VerticalAlignment verticalAlignment;
|
||||
|
||||
// We use a boxed Boolean internally in order to be able to model the absence of a value.
|
||||
// For callers outside it should expose only the primitive though.
|
||||
private Boolean wordBreak;
|
||||
|
||||
public boolean isWordBreak() {
|
||||
return wordBreak != null && wordBreak;
|
||||
}
|
||||
|
||||
public void setWordBreak(boolean wordBreak) {
|
||||
this.wordBreak = wordBreak;
|
||||
}
|
||||
|
||||
public void fillingMergeBy(Settings settings) {
|
||||
fillingMergeFontSettings(settings);
|
||||
fillingMergePaddingSettings(settings);
|
||||
fillingMergeBorderWidthSettings(settings);
|
||||
fillingMergeBorderStyleSettings(settings);
|
||||
fillingMergeColorSettings(settings);
|
||||
fillingMergeAlignmentSettings(settings);
|
||||
fillingMergeWordBreakSetting(settings);
|
||||
}
|
||||
|
||||
public Boolean getWordBreak() {
|
||||
return wordBreak;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleBottom() {
|
||||
return borderStyleBottom;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleLeft() {
|
||||
return borderStyleLeft;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleRight() {
|
||||
return borderStyleRight;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyleTop() {
|
||||
return borderStyleTop;
|
||||
}
|
||||
|
||||
public Color getBackgroundColor() {
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
public Color getBorderColor() {
|
||||
return borderColor;
|
||||
}
|
||||
|
||||
public Color getTextColor() {
|
||||
return textColor;
|
||||
}
|
||||
|
||||
public Float getBorderWidthBottom() {
|
||||
return borderWidthBottom;
|
||||
}
|
||||
|
||||
public Float getBorderWidthLeft() {
|
||||
return borderWidthLeft;
|
||||
}
|
||||
|
||||
public Float getBorderWidthRight() {
|
||||
return borderWidthRight;
|
||||
}
|
||||
|
||||
public Float getBorderWidthTop() {
|
||||
return borderWidthTop;
|
||||
}
|
||||
|
||||
public Float getPaddingBottom() {
|
||||
return paddingBottom;
|
||||
}
|
||||
|
||||
public Float getPaddingLeft() {
|
||||
return paddingLeft;
|
||||
}
|
||||
|
||||
public Float getPaddingRight() {
|
||||
return paddingRight;
|
||||
}
|
||||
|
||||
public Float getPaddingTop() {
|
||||
return paddingTop;
|
||||
}
|
||||
|
||||
public HorizontalAlignment getHorizontalAlignment() {
|
||||
return horizontalAlignment;
|
||||
}
|
||||
|
||||
public Integer getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public VerticalAlignment getVerticalAlignment() {
|
||||
return verticalAlignment;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(Color backgroundColor) {
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(Color borderColor) {
|
||||
this.borderColor = borderColor;
|
||||
}
|
||||
|
||||
public void setBorderStyleBottom(BorderStyleInterface borderStyleBottom) {
|
||||
this.borderStyleBottom = borderStyleBottom;
|
||||
}
|
||||
|
||||
public void setBorderStyleLeft(BorderStyleInterface borderStyleLeft) {
|
||||
this.borderStyleLeft = borderStyleLeft;
|
||||
}
|
||||
|
||||
public void setBorderStyleRight(BorderStyleInterface borderStyleRight) {
|
||||
this.borderStyleRight = borderStyleRight;
|
||||
}
|
||||
|
||||
public void setBorderStyleTop(BorderStyleInterface borderStyleTop) {
|
||||
this.borderStyleTop = borderStyleTop;
|
||||
}
|
||||
|
||||
public void setBorderWidthBottom(Float borderWidthBottom) {
|
||||
this.borderWidthBottom = borderWidthBottom;
|
||||
}
|
||||
|
||||
public void setBorderWidthLeft(Float borderWidthLeft) {
|
||||
this.borderWidthLeft = borderWidthLeft;
|
||||
}
|
||||
|
||||
public void setBorderWidthRight(Float borderWidthRight) {
|
||||
this.borderWidthRight = borderWidthRight;
|
||||
}
|
||||
|
||||
public void setBorderWidthTop(Float borderWidthTop) {
|
||||
this.borderWidthTop = borderWidthTop;
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public void setFontSize(Integer fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
public void setHorizontalAlignment(HorizontalAlignment horizontalAlignment) {
|
||||
this.horizontalAlignment = horizontalAlignment;
|
||||
}
|
||||
|
||||
public void setPaddingBottom(Float paddingBottom) {
|
||||
this.paddingBottom = paddingBottom;
|
||||
}
|
||||
|
||||
public void setPaddingLeft(Float paddingLeft) {
|
||||
this.paddingLeft = paddingLeft;
|
||||
}
|
||||
|
||||
public void setPaddingRight(Float paddingRight) {
|
||||
this.paddingRight = paddingRight;
|
||||
}
|
||||
|
||||
public void setPaddingTop(Float paddingTop) {
|
||||
this.paddingTop = paddingTop;
|
||||
}
|
||||
|
||||
public void setTextColor(Color textColor) {
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
public void setVerticalAlignment(VerticalAlignment verticalAlignment) {
|
||||
this.verticalAlignment = verticalAlignment;
|
||||
}
|
||||
|
||||
public void setWordBreak(Boolean wordBreak) {
|
||||
this.wordBreak = wordBreak;
|
||||
}
|
||||
|
||||
private void fillingMergeWordBreakSetting(Settings settings) {
|
||||
// Note that we use the boxed Boolean only here internally!
|
||||
if (wordBreak == null && settings.wordBreak != null) {
|
||||
wordBreak = settings.getWordBreak();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergePaddingSettings(Settings settings) {
|
||||
if (getPaddingBottom() == null && settings.getPaddingBottom() != null) {
|
||||
paddingBottom = settings.getPaddingBottom();
|
||||
}
|
||||
|
||||
if (getPaddingTop() == null && settings.getPaddingTop() != null) {
|
||||
paddingTop = settings.getPaddingTop();
|
||||
}
|
||||
|
||||
if (getPaddingLeft() == null && settings.getPaddingLeft() != null) {
|
||||
paddingLeft = settings.getPaddingLeft();
|
||||
}
|
||||
|
||||
if (getPaddingRight() == null && settings.getPaddingRight() != null) {
|
||||
paddingRight = settings.getPaddingRight();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergeBorderWidthSettings(Settings settings) {
|
||||
if (getBorderWidthBottom() == null && settings.getBorderWidthBottom() != null) {
|
||||
borderWidthBottom = settings.getBorderWidthBottom();
|
||||
}
|
||||
|
||||
if (getBorderWidthTop() == null && settings.getBorderWidthTop() != null) {
|
||||
borderWidthTop = settings.getBorderWidthTop();
|
||||
}
|
||||
|
||||
if (getBorderWidthLeft() == null && settings.getBorderWidthLeft() != null) {
|
||||
borderWidthLeft = settings.getBorderWidthLeft();
|
||||
}
|
||||
|
||||
if (getBorderWidthRight() == null && settings.getBorderWidthRight() != null) {
|
||||
borderWidthRight = settings.getBorderWidthRight();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergeBorderStyleSettings(Settings settings) {
|
||||
if (getBorderStyleBottom() == null && settings.getBorderStyleBottom() != null) {
|
||||
borderStyleBottom = settings.getBorderStyleBottom();
|
||||
}
|
||||
|
||||
if (getBorderStyleTop() == null && settings.getBorderStyleTop() != null) {
|
||||
borderStyleTop = settings.getBorderStyleTop();
|
||||
}
|
||||
|
||||
if (getBorderStyleLeft() == null && settings.getBorderStyleLeft() != null) {
|
||||
borderStyleLeft = settings.getBorderStyleLeft();
|
||||
}
|
||||
|
||||
if (getBorderStyleRight() == null && settings.getBorderStyleRight() != null) {
|
||||
borderStyleRight = settings.getBorderStyleRight();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergeColorSettings(Settings settings) {
|
||||
if (getTextColor() == null && settings.getTextColor() != null) {
|
||||
textColor = settings.getTextColor();
|
||||
}
|
||||
|
||||
if (getBackgroundColor() == null && settings.getBackgroundColor() != null) {
|
||||
backgroundColor = settings.getBackgroundColor();
|
||||
}
|
||||
|
||||
if (getBorderColor() == null && settings.getBorderColor() != null) {
|
||||
borderColor = settings.getBorderColor();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergeAlignmentSettings(Settings settings) {
|
||||
if (getHorizontalAlignment() == null && settings.getHorizontalAlignment() != null) {
|
||||
horizontalAlignment = settings.getHorizontalAlignment();
|
||||
}
|
||||
|
||||
if (getVerticalAlignment() == null && settings.getVerticalAlignment() != null) {
|
||||
verticalAlignment = settings.getVerticalAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
private void fillingMergeFontSettings(Settings settings) {
|
||||
if (getFont() == null && settings.getFont() != null) {
|
||||
font = settings.getFont();
|
||||
}
|
||||
if (getFontSize() == null && settings.getFontSize() != null) {
|
||||
fontSize = settings.getFontSize();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class StyledText implements ParagraphProcessor {
|
||||
|
||||
private String text;
|
||||
|
||||
private Float fontSize;
|
||||
|
||||
private Font font;
|
||||
|
||||
private Color color;
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setFontSize(Float fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
public Float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Paragraph paragraph, Settings settings) {
|
||||
final float actualFontSize = getFontSize() != null ? getFontSize() : settings.getFontSize();
|
||||
final Font actualFont = getFont() != null ? getFont() : settings.getFont();
|
||||
final Color actualColor = getColor() != null ? getColor() : settings.getTextColor();
|
||||
// TODO this is a complete mess to handle new lines!!!
|
||||
String[] lines = getText().split(PdfUtil.NEW_LINE_REGEX);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
FontDescriptor fontDescriptor = new FontDescriptor(actualFont, actualFontSize);
|
||||
paragraph.add(new org.xbib.graphics.pdfbox.layout.text.StyledText(lines[i], fontDescriptor, actualColor, 0f, 0, 0));
|
||||
if (i < lines.length - 1) {
|
||||
paragraph.add(new org.xbib.graphics.pdfbox.layout.text.NewLine(new FontDescriptor(actualFont, actualFontSize)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.BaseFont;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class Table {
|
||||
|
||||
private static final Font DEFAULT_FONT = BaseFont.HELVETICA;
|
||||
|
||||
private static final int DEFAULT_FONT_SIZE = 12;
|
||||
|
||||
private static final Color DEFAULT_TEXT_COLOR = Color.BLACK;
|
||||
|
||||
private static final Color DEFAULT_BORDER_COLOR = Color.BLACK;
|
||||
|
||||
private static final BorderStyleInterface DEFAULT_BORDER_STYLE = BorderStyle.SOLID;
|
||||
|
||||
private static final float DEFAULT_PADDING = 4f;
|
||||
|
||||
private static final HorizontalAlignment DEFAULT_HORIZONTAL_ALIGNMENT = HorizontalAlignment.LEFT;
|
||||
|
||||
private static final VerticalAlignment DEFAULT_VERTICAL_ALIGNMENT = VerticalAlignment.MIDDLE;
|
||||
|
||||
private final List<Row> rows;
|
||||
|
||||
private final List<Column> columns;
|
||||
|
||||
private final Set<Point> rowSpanCells;
|
||||
|
||||
private Settings settings;
|
||||
|
||||
private int numberOfColumns;
|
||||
|
||||
private float width;
|
||||
|
||||
public Table(List<Row> rows, List<Column> columns, Set<Point> rowSpanCells) {
|
||||
this.rows = rows;
|
||||
this.columns = columns;
|
||||
this.rowSpanCells = rowSpanCells;
|
||||
}
|
||||
|
||||
public void setSettings(Settings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public Settings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public static BorderStyleInterface getDefaultBorderStyle() {
|
||||
return DEFAULT_BORDER_STYLE;
|
||||
}
|
||||
|
||||
public static Color getDefaultBorderColor() {
|
||||
return DEFAULT_BORDER_COLOR;
|
||||
}
|
||||
|
||||
public static Color getDefaultTextColor() {
|
||||
return DEFAULT_TEXT_COLOR;
|
||||
}
|
||||
|
||||
public static float getDefaultPadding() {
|
||||
return DEFAULT_PADDING;
|
||||
}
|
||||
|
||||
public static HorizontalAlignment getDefaultHorizontalAlignment() {
|
||||
return DEFAULT_HORIZONTAL_ALIGNMENT;
|
||||
}
|
||||
|
||||
public static int getDefaultFontSize() {
|
||||
return DEFAULT_FONT_SIZE;
|
||||
}
|
||||
|
||||
public int getNumberOfColumns() {
|
||||
return numberOfColumns;
|
||||
}
|
||||
|
||||
public List<Column> getColumns() {
|
||||
return columns;
|
||||
}
|
||||
|
||||
public List<Row> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public static Font getDefaultFont() {
|
||||
return DEFAULT_FONT;
|
||||
}
|
||||
|
||||
public Set<Point> getRowSpanCells() {
|
||||
return rowSpanCells;
|
||||
}
|
||||
|
||||
public static VerticalAlignment getDefaultVerticalAlignment() {
|
||||
return DEFAULT_VERTICAL_ALIGNMENT;
|
||||
}
|
||||
|
||||
public void setNumberOfColumns(int numberOfColumns) {
|
||||
this.numberOfColumns = numberOfColumns;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return rows.stream().map(Row::getHeight).reduce(0F, Float::sum);
|
||||
}
|
||||
|
||||
public boolean isRowSpanAt(int rowIndex, int columnIndex) {
|
||||
return rowSpanCells.contains(new Point(rowIndex, columnIndex));
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Settings settings = new Settings();
|
||||
|
||||
private final List<Row> rows = new ArrayList<>();
|
||||
|
||||
private final List<Column> columns = new ArrayList<>();
|
||||
|
||||
private final Set<Point> rowSpanCells = new HashSet<>();
|
||||
|
||||
private int numberOfColumns;
|
||||
|
||||
private float width;
|
||||
|
||||
private Builder() {
|
||||
settings.setFont(DEFAULT_FONT);
|
||||
settings.setFontSize(DEFAULT_FONT_SIZE);
|
||||
settings.setTextColor(DEFAULT_TEXT_COLOR);
|
||||
settings.setBorderColor(DEFAULT_BORDER_COLOR);
|
||||
settings.setBorderStyleTop(DEFAULT_BORDER_STYLE);
|
||||
settings.setBorderStyleBottom(DEFAULT_BORDER_STYLE);
|
||||
settings.setBorderStyleLeft(DEFAULT_BORDER_STYLE);
|
||||
settings.setBorderStyleRight(DEFAULT_BORDER_STYLE);
|
||||
settings.setPaddingTop(DEFAULT_PADDING);
|
||||
settings.setPaddingBottom(DEFAULT_PADDING);
|
||||
settings.setPaddingLeft(DEFAULT_PADDING);
|
||||
settings.setPaddingRight(DEFAULT_PADDING);
|
||||
settings.setWordBreak(true);
|
||||
}
|
||||
|
||||
public Builder addRow(final Row row) {
|
||||
// Store how many cells can or better have to be omitted in the next rows
|
||||
// due to cells in this row that declare row spanning
|
||||
updateRowSpanCellsSet(row.getCells());
|
||||
|
||||
if (!rows.isEmpty()) {
|
||||
rows.get(rows.size() - 1).setNext(row);
|
||||
}
|
||||
rows.add(row);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private float getAvailableCellWidthRespectingSpan(int columnIndex, int span) {
|
||||
float cellWidth = 0;
|
||||
for (int i = 0; i < span; i++) {
|
||||
cellWidth += columns.get(columnIndex + i).getWidth();
|
||||
}
|
||||
return cellWidth;
|
||||
}
|
||||
|
||||
// This method is unfortunately a bit complex, but what it does is basically:
|
||||
// Put every cell coordinate in the set which needs to be skipped because it is
|
||||
// "contained" in another cell due to row spanning.
|
||||
// The coordinates are those of the table how it would look like without any spanning.
|
||||
private void updateRowSpanCellsSet(List<AbstractCell> cells) {
|
||||
int currentColumn = 0;
|
||||
|
||||
for (AbstractCell cell : cells) {
|
||||
|
||||
while (rowSpanCells.contains(new Point(rows.size(), currentColumn))) {
|
||||
currentColumn++;
|
||||
}
|
||||
|
||||
if (cell.getRowSpan() > 1) {
|
||||
|
||||
for (int rowsToSpan = 0; rowsToSpan < cell.getRowSpan(); rowsToSpan++) {
|
||||
|
||||
// Skip first row's cell, because that is a regular cell
|
||||
if (rowsToSpan >= 1) {
|
||||
for (int colSpan = 0; colSpan < cell.getColSpan(); colSpan++) {
|
||||
rowSpanCells.add(new Point(rows.size() + rowsToSpan, currentColumn + colSpan));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentColumn += cell.getColSpan();
|
||||
}
|
||||
}
|
||||
|
||||
public Builder addColumnsOfWidth(final float... columnWidths) {
|
||||
for (float columnWidth : columnWidths) {
|
||||
addColumnOfWidth(columnWidth);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addColumnOfWidth(final float width) {
|
||||
Column column = new Column(width);
|
||||
numberOfColumns++;
|
||||
columns.add(column);
|
||||
this.width += column.getWidth();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder font(Font font) {
|
||||
settings.setFont(font);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fontSize(final Integer fontSize) {
|
||||
settings.setFontSize(fontSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder textColor(final Color textColor) {
|
||||
settings.setTextColor(textColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder backgroundColor(final Color backgroundColor) {
|
||||
settings.setBackgroundColor(backgroundColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidth(final float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyle(final BorderStyleInterface borderStyle) {
|
||||
settings.setBorderStyleTop(borderStyle);
|
||||
settings.setBorderStyleBottom(borderStyle);
|
||||
settings.setBorderStyleLeft(borderStyle);
|
||||
settings.setBorderStyleRight(borderStyle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder padding(final float padding) {
|
||||
settings.setPaddingTop(padding);
|
||||
settings.setPaddingBottom(padding);
|
||||
settings.setPaddingLeft(padding);
|
||||
settings.setPaddingRight(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderColor(final Color borderColor) {
|
||||
settings.setBorderColor(borderColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder horizontalAlignment(HorizontalAlignment alignment) {
|
||||
settings.setHorizontalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder verticalAlignment(VerticalAlignment alignment) {
|
||||
settings.setVerticalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder wordBreak(Boolean wordBreak) {
|
||||
settings.setWordBreak(wordBreak);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Table build() {
|
||||
if (getNumberOfRegularCells() != getNumberOfSpannedCells()) {
|
||||
throw new TableSetupException("Number of table cells does not match with table setup. " +
|
||||
"This could be due to row or col spanning not being correct");
|
||||
}
|
||||
Table table = new Table(rows, columns, rowSpanCells);
|
||||
table.setSettings(settings);
|
||||
table.setWidth(width);
|
||||
table.setNumberOfColumns(numberOfColumns);
|
||||
setupConnectionsBetweenElementsFor(table);
|
||||
correctHeightOfCellsDueToRowSpanningIfNecessaryFor(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
private void setupConnectionsBetweenElementsFor(Table table) {
|
||||
for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
|
||||
Row row = rows.get(rowIndex);
|
||||
row.setTable(table);
|
||||
if (table.getSettings() != null) {
|
||||
row.getSettings().fillingMergeBy(table.getSettings());
|
||||
}
|
||||
int columnIndex = 0;
|
||||
for (AbstractCell cell : row.getCells()) {
|
||||
cell.getSettings().fillingMergeBy(row.getSettings());
|
||||
cell.setRow(row);
|
||||
while (table.isRowSpanAt(rowIndex, columnIndex)) {
|
||||
columnIndex++;
|
||||
}
|
||||
Column column = table.getColumns().get(columnIndex);
|
||||
cell.setColumn(column);
|
||||
cell.setWidth(getAvailableCellWidthRespectingSpan(columnIndex, cell.getColSpan()));
|
||||
columnIndex += cell.getColSpan();
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < table.getColumns().size(); i++) {
|
||||
final Column column = table.getColumns().get(i);
|
||||
column.setTable(table);
|
||||
if (i < table.getColumns().size() - 1) {
|
||||
column.setNext(table.getColumns().get(i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void correctHeightOfCellsDueToRowSpanningIfNecessaryFor(Table table) {
|
||||
for (int i = 0; i < table.getRows().size(); i++) {
|
||||
final Optional<AbstractCell> highestSpanningCell = rows.get(i).getCells().stream()
|
||||
.filter(x -> x.getRowSpan() > 1)
|
||||
.max(Comparator.comparing(AbstractCell::getMinHeight));
|
||||
if (highestSpanningCell.isPresent()) {
|
||||
final float heightOfHighestCell = highestSpanningCell.get().getMinHeight();
|
||||
float regularHeightOfRows = 0;
|
||||
for (int j = i; j < i + highestSpanningCell.get().getRowSpan(); j++) {
|
||||
regularHeightOfRows += rows.get(j).getHeight();
|
||||
}
|
||||
if (heightOfHighestCell > regularHeightOfRows) {
|
||||
for (int k = 0; k < table.getColumns().size(); k++) {
|
||||
float rowsHeight = 0;
|
||||
for (int l = i; l < (i + highestSpanningCell.get().getRowSpan()); l++) {
|
||||
rowsHeight += table.getRows().get(l).getHeight();
|
||||
}
|
||||
for (int l = i; l < (i + highestSpanningCell.get().getRowSpan()); l++) {
|
||||
final Row rowThatNeedsAdaption = table.getRows().get(l);
|
||||
rowThatNeedsAdaption.doRowSpanSizeAdaption(heightOfHighestCell, rowsHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getNumberOfRegularCells() {
|
||||
return columns.size() * rows.size();
|
||||
}
|
||||
|
||||
private int getNumberOfSpannedCells() {
|
||||
return rows.stream()
|
||||
.flatMapToInt(row -> row.getCells().stream().mapToInt(cell -> cell.getRowSpan() * cell.getColSpan()))
|
||||
.sum();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TableNotYetBuiltException extends RuntimeException {
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.RenderContext;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.IOException;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode.APPEND;
|
||||
|
||||
public class TableRenderer {
|
||||
|
||||
protected final Table table;
|
||||
|
||||
protected PDPageContentStream contentStream;
|
||||
|
||||
protected PDPage page;
|
||||
|
||||
protected float startX;
|
||||
|
||||
protected float startY;
|
||||
|
||||
protected float endY;
|
||||
|
||||
protected boolean compress;
|
||||
|
||||
protected PDPage tableStartPage;
|
||||
|
||||
protected boolean startTableInNewPage;
|
||||
|
||||
protected final List<BiConsumer<Renderer, RenderContext>> drawerList = new LinkedList<>();
|
||||
|
||||
{
|
||||
this.drawerList.add((drawer, drawingContext) -> {
|
||||
drawer.renderBackground(drawingContext);
|
||||
drawer.renderContent(drawingContext);
|
||||
});
|
||||
this.drawerList.add(Renderer::renderBorders);
|
||||
}
|
||||
|
||||
public TableRenderer(Table table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public void setStartX(float startX) {
|
||||
this.startX = startX;
|
||||
}
|
||||
|
||||
public void setStartY(float startY) {
|
||||
this.startY = startY;
|
||||
}
|
||||
|
||||
public void setEndY(float endY) {
|
||||
this.endY = endY;
|
||||
}
|
||||
|
||||
public void setContentStream(PDPageContentStream contentStream) {
|
||||
this.contentStream = contentStream;
|
||||
}
|
||||
|
||||
public void setPage(PDPage page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
drawPage(new PageData(0, table.getRows().size()));
|
||||
}
|
||||
|
||||
protected void drawPage(PageData pageData) {
|
||||
drawerList.forEach(drawer -> drawWithFunction(pageData, new Point2D.Float(this.startX, this.startY), drawer));
|
||||
}
|
||||
|
||||
protected Queue<PageData> computeRowsOnPagesWithNewPageStartOf(float yOffsetOnNewPage) {
|
||||
final Queue<PageData> dataForPages = new LinkedList<>();
|
||||
float y = startY;
|
||||
int firstRowOnPage = 0;
|
||||
int lastRowOnPage = 0;
|
||||
for (final Row row : table.getRows()) {
|
||||
if (isRowTooHighToBeDrawnOnPage(row, yOffsetOnNewPage)) {
|
||||
throw new RowIsTooHighException("There is a row that is too high to be drawn on a single page");
|
||||
}
|
||||
if (isNotDrawableOnPage(y, row)) {
|
||||
dataForPages.add(new PageData(firstRowOnPage, lastRowOnPage));
|
||||
y = yOffsetOnNewPage;
|
||||
firstRowOnPage = lastRowOnPage;
|
||||
}
|
||||
y -= row.getHeight();
|
||||
lastRowOnPage++;
|
||||
}
|
||||
dataForPages.add(new PageData(firstRowOnPage, lastRowOnPage));
|
||||
return dataForPages;
|
||||
}
|
||||
|
||||
private boolean isRowTooHighToBeDrawnOnPage(Row row, float yOffsetOnNewPage) {
|
||||
return row.getHeight() > (yOffsetOnNewPage - endY);
|
||||
}
|
||||
|
||||
protected void determinePageToStartTable(float yOffsetOnNewPage) {
|
||||
if (startY - table.getRows().get(0).getHeight() < endY) {
|
||||
startY = yOffsetOnNewPage;
|
||||
startTableInNewPage = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(Supplier<PDDocument> documentSupplier, Supplier<PDPage> pageSupplier, float yOffset) throws IOException {
|
||||
PDDocument document = documentSupplier.get();
|
||||
float startOnNewPage = pageSupplier.get().getMediaBox().getHeight() - yOffset;
|
||||
determinePageToStartTable(startOnNewPage);
|
||||
final Queue<PageData> pageDataQueue = computeRowsOnPagesWithNewPageStartOf(startOnNewPage);
|
||||
for (int i = 0; !pageDataQueue.isEmpty(); i++) {
|
||||
final PDPage pageToDrawOn = determinePageToDraw(i, document, pageSupplier);
|
||||
if ((i == 0 && startTableInNewPage) || i > 0 || document.getNumberOfPages() == 0) {
|
||||
startTableInNewPage = false;
|
||||
}
|
||||
if (i == 0) {
|
||||
tableStartPage = pageToDrawOn;
|
||||
}
|
||||
try (PDPageContentStream newPageContentStream = new PDPageContentStream(document, pageToDrawOn, APPEND, compress)) {
|
||||
setContentStream(newPageContentStream);
|
||||
setPage(pageToDrawOn);
|
||||
drawPage(pageDataQueue.poll());
|
||||
}
|
||||
setStartY(pageToDrawOn.getMediaBox().getHeight() - yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
protected PDPage determinePageToDraw(int index, PDDocument document, Supplier<PDPage> pageSupplier) {
|
||||
final PDPage pageToDrawOn;
|
||||
if ((index == 0 && startTableInNewPage) || index > 0 || document.getNumberOfPages() == 0) {
|
||||
pageToDrawOn = pageSupplier.get();
|
||||
document.addPage(pageToDrawOn);
|
||||
} else {
|
||||
pageToDrawOn = document.getPage(document.getNumberOfPages() - 1);
|
||||
}
|
||||
return pageToDrawOn;
|
||||
}
|
||||
|
||||
protected void drawWithFunction(PageData pageData, Point2D.Float startingPoint, BiConsumer<Renderer, RenderContext> consumer) {
|
||||
float y = startingPoint.y;
|
||||
for (int rowIndex = pageData.firstRowOnPage; rowIndex < pageData.firstRowOnNextPage; rowIndex++) {
|
||||
final Row row = table.getRows().get(rowIndex);
|
||||
y -= row.getHeight();
|
||||
drawRow(new Point2D.Float(startingPoint.x, y), row, rowIndex, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
protected void drawRow(Point2D.Float start, Row row, int rowIndex, BiConsumer<Renderer, RenderContext> consumer) {
|
||||
float x = start.x;
|
||||
int columnCounter = 0;
|
||||
for (AbstractCell cell : row.getCells()) {
|
||||
while (table.isRowSpanAt(rowIndex, columnCounter)) {
|
||||
x += table.getColumns().get(columnCounter).getWidth();
|
||||
columnCounter++;
|
||||
}
|
||||
consumer.accept(cell.getDrawer(), new RenderContext(contentStream, page, new Point2D.Float(x, start.y)));
|
||||
x += cell.getWidth();
|
||||
columnCounter += cell.getColSpan();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotDrawableOnPage(float startY, Row row) {
|
||||
return startY - getHighestCellOf(row) < endY;
|
||||
}
|
||||
|
||||
private Float getHighestCellOf(Row row) {
|
||||
return row.getCells().stream()
|
||||
.map(AbstractCell::getHeight)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(row.getHeight());
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Table table;
|
||||
|
||||
private PDPageContentStream contentStream;
|
||||
|
||||
private float startX;
|
||||
|
||||
private float startY;
|
||||
|
||||
private float endY;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder table(Table table) {
|
||||
this.table = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder contentStream(PDPageContentStream contentStream) {
|
||||
this.contentStream = contentStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder startX(float startX) {
|
||||
this.startX = startX;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder startY(float startY) {
|
||||
this.startY = startY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder endY(float endY) {
|
||||
this.endY = endY;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TableRenderer build() {
|
||||
TableRenderer tableRenderer = new TableRenderer(table);
|
||||
tableRenderer.setContentStream(contentStream);
|
||||
tableRenderer.setStartX(startX);
|
||||
tableRenderer.setStartY(startY);
|
||||
tableRenderer.setEndY(endY);
|
||||
return tableRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PageData {
|
||||
|
||||
public final int firstRowOnPage;
|
||||
|
||||
public final int firstRowOnNextPage;
|
||||
|
||||
public PageData(int firstRowOnPage, int firstRowOnNextPage) {
|
||||
this.firstRowOnPage = firstRowOnPage;
|
||||
this.firstRowOnNextPage = firstRowOnNextPage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TableSetupException extends RuntimeException {
|
||||
|
||||
public TableSetupException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.TextCellRenderer;
|
||||
import java.awt.Color;
|
||||
|
||||
public class TextCell extends AbstractTextCell {
|
||||
|
||||
protected String text;
|
||||
|
||||
protected Renderer createDefaultDrawer() {
|
||||
return new TextCellRenderer<>(this);
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Settings settings;
|
||||
|
||||
private String text;
|
||||
|
||||
private int colSpan;
|
||||
|
||||
private int rowSpan;
|
||||
|
||||
private Builder() {
|
||||
settings = new Settings();
|
||||
}
|
||||
|
||||
public Builder text(String text) {
|
||||
this.text = text;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder font(Font font) {
|
||||
settings.setFont(font);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fontSize(Integer fontSize) {
|
||||
settings.setFontSize(fontSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder textColor(Color textColor) {
|
||||
settings.setTextColor(textColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidth(float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidthTop(final float borderWidth) {
|
||||
settings.setBorderWidthTop(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidthBottom(final float borderWidth) {
|
||||
settings.setBorderWidthBottom(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidthLeft(final float borderWidth) {
|
||||
settings.setBorderWidthLeft(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderWidthRight(final float borderWidth) {
|
||||
settings.setBorderWidthRight(borderWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyleTop(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleTop(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyleBottom(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleBottom(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyleLeft(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleLeft(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyleRight(final BorderStyleInterface style) {
|
||||
settings.setBorderStyleRight(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderStyle(final BorderStyleInterface style) {
|
||||
return this.borderStyleLeft(style)
|
||||
.borderStyleRight(style)
|
||||
.borderStyleBottom(style)
|
||||
.borderStyleTop(style);
|
||||
}
|
||||
|
||||
public Builder padding(final float padding) {
|
||||
return this.paddingTop(padding)
|
||||
.paddingBottom(padding)
|
||||
.paddingLeft(padding)
|
||||
.paddingRight(padding);
|
||||
}
|
||||
|
||||
public Builder paddingTop(final float padding) {
|
||||
settings.setPaddingTop(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder paddingBottom(final float padding) {
|
||||
settings.setPaddingBottom(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder paddingLeft(final float padding) {
|
||||
settings.setPaddingLeft(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder paddingRight(final float padding) {
|
||||
settings.setPaddingRight(padding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder horizontalAlignment(final HorizontalAlignment alignment) {
|
||||
settings.setHorizontalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder verticalAlignment(final VerticalAlignment alignment) {
|
||||
settings.setVerticalAlignment(alignment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder backgroundColor(final Color backgroundColor) {
|
||||
settings.setBackgroundColor(backgroundColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder borderColor(final Color borderColor) {
|
||||
settings.setBorderColor(borderColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder wordBreak(final Boolean wordBreak) {
|
||||
settings.setWordBreak(wordBreak);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder colSpan(int colSpan) {
|
||||
this.colSpan = colSpan;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder rowSpan(int rowSpan) {
|
||||
this.rowSpan = rowSpan;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextCell build() {
|
||||
TextCell cell = new TextCell();
|
||||
cell.setSettings(settings);
|
||||
cell.setText(text);
|
||||
if (colSpan > 0) {
|
||||
cell.setColSpan(colSpan);
|
||||
}
|
||||
if (rowSpan > 0) {
|
||||
cell.setRowSpan(rowSpan);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
public enum VerticalAlignment {
|
||||
|
||||
BOTTOM, MIDDLE, TOP
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.Renderer;
|
||||
import org.xbib.graphics.pdfbox.layout.table.render.VerticalTextCellRenderer;
|
||||
|
||||
public class VerticalTextCell extends AbstractTextCell {
|
||||
|
||||
private String text;
|
||||
|
||||
protected Renderer createDefaultDrawer() {
|
||||
return new VerticalTextCellRenderer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import static org.xbib.graphics.pdfbox.layout.table.VerticalAlignment.BOTTOM;
|
||||
import static org.xbib.graphics.pdfbox.layout.table.VerticalAlignment.MIDDLE;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.table.AbstractCell;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public abstract class AbstractCellRenderer<T extends AbstractCell> implements Renderer {
|
||||
|
||||
protected T cell;
|
||||
|
||||
public AbstractCellRenderer<T> withCell(T cell) {
|
||||
this.cell = cell;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderBackground(RenderContext renderContext) {
|
||||
if (cell.hasBackgroundColor()) {
|
||||
final PDPageContentStream contentStream = renderContext.getContentStream();
|
||||
final Point2D.Float start = renderContext.getStartingPoint();
|
||||
final float rowHeight = cell.getRow().getHeight();
|
||||
final float height = Math.max(cell.getHeight(), rowHeight);
|
||||
final float y = rowHeight < cell.getHeight()
|
||||
? start.y + rowHeight - cell.getHeight()
|
||||
: start.y;
|
||||
PositionedRectangle positionedRectangle =
|
||||
new PositionedRectangle(start.x, y, cell.getWidth(), height, cell.getBackgroundColor());
|
||||
RenderUtil.drawRectangle(contentStream, positionedRectangle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void renderContent(RenderContext renderContext);
|
||||
|
||||
@Override
|
||||
public void renderBorders(RenderContext renderContext) {
|
||||
final Point2D.Float start = renderContext.getStartingPoint();
|
||||
final PDPageContentStream contentStream = renderContext.getContentStream();
|
||||
final float cellWidth = cell.getWidth();
|
||||
final float rowHeight = cell.getRow().getHeight();
|
||||
final float height = Math.max(cell.getHeight(), rowHeight);
|
||||
final float sY = rowHeight < cell.getHeight()
|
||||
? start.y + rowHeight - cell.getHeight()
|
||||
: start.y;
|
||||
final Color cellBorderColor = cell.getBorderColor();
|
||||
final Color rowBorderColor = cell.getRow().getSettings().getBorderColor();
|
||||
if (cell.hasBorderTop() || cell.hasBorderBottom()) {
|
||||
final float correctionLeft = cell.getBorderWidthLeft() / 2;
|
||||
final float correctionRight = cell.getBorderWidthRight() / 2;
|
||||
if (cell.hasBorderTop()) {
|
||||
PositionedLine line = new PositionedLine();
|
||||
line.setStartX(start.x - correctionLeft);
|
||||
line.setStartY(start.y + rowHeight);
|
||||
line.setEndX(start.x + cellWidth + correctionRight);
|
||||
line.setEndY(start.y + rowHeight);
|
||||
line.setWidth(cell.getBorderWidthTop());
|
||||
line.setColor(cellBorderColor);
|
||||
line.setResetColor(rowBorderColor);
|
||||
line.setBorderStyle(cell.getBorderStyleTop());
|
||||
RenderUtil.drawLine(contentStream, line);
|
||||
}
|
||||
if (cell.hasBorderBottom()) {
|
||||
PositionedLine line = new PositionedLine();
|
||||
line.setStartX(start.x - correctionLeft);
|
||||
line.setStartY(sY);
|
||||
line.setEndX(start.x + cellWidth + correctionRight);
|
||||
line.setEndY(sY);
|
||||
line.setWidth(cell.getBorderWidthBottom());
|
||||
line.setColor(cellBorderColor);
|
||||
line.setResetColor(rowBorderColor);
|
||||
line.setBorderStyle(cell.getBorderStyleBottom());
|
||||
RenderUtil.drawLine(contentStream, line);
|
||||
}
|
||||
}
|
||||
if (cell.hasBorderLeft() || cell.hasBorderRight()) {
|
||||
final float correctionTop = cell.getBorderWidthTop() / 2;
|
||||
final float correctionBottom = cell.getBorderWidthBottom() / 2;
|
||||
if (cell.hasBorderLeft()) {
|
||||
PositionedLine line = new PositionedLine();
|
||||
line.setStartX(start.x);
|
||||
line.setStartY(sY - correctionBottom);
|
||||
line.setEndX(start.x);
|
||||
line.setEndY(sY + height + correctionTop);
|
||||
line.setWidth(cell.getBorderWidthLeft());
|
||||
line.setColor(cellBorderColor);
|
||||
line.setResetColor(rowBorderColor);
|
||||
line.setBorderStyle(cell.getBorderStyleLeft());
|
||||
RenderUtil.drawLine(contentStream, line);
|
||||
}
|
||||
if (cell.hasBorderRight()) {
|
||||
PositionedLine line = new PositionedLine();
|
||||
line.setStartX(start.x + cellWidth);
|
||||
line.setStartY(sY - correctionBottom);
|
||||
line.setEndX(start.x + cellWidth);
|
||||
line.setEndY(sY + height + correctionTop);
|
||||
line.setWidth(cell.getBorderWidthRight());
|
||||
line.setColor(cellBorderColor);
|
||||
line.setResetColor(rowBorderColor);
|
||||
line.setBorderStyle(cell.getBorderStyleRight());
|
||||
RenderUtil.drawLine(contentStream, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean rowHeightIsBiggerThanOrEqualToCellHeight() {
|
||||
return cell.getRow().getHeight() > cell.getHeight() ||
|
||||
isEqualInEpsilon(cell.getRow().getHeight(), cell.getHeight());
|
||||
}
|
||||
|
||||
protected float getRowSpanAdaption() {
|
||||
return cell.getRowSpan() > 1 ? cell.calculateHeightForRowSpan() - cell.getRow().getHeight() : 0;
|
||||
}
|
||||
|
||||
protected float calculateOuterHeight() {
|
||||
return cell.getRowSpan() > 1 ? cell.getHeight() : cell.getRow().getHeight();
|
||||
}
|
||||
|
||||
protected float getAdaptionForVerticalAlignment() {
|
||||
if (rowHeightIsBiggerThanOrEqualToCellHeight() || cell.getRowSpan() > 1) {
|
||||
if (cell.isVerticallyAligned(MIDDLE)) {
|
||||
return (calculateOuterHeight() / 2 + calculateInnerHeight() / 2) - getRowSpanAdaption();
|
||||
} else if (cell.isVerticallyAligned(BOTTOM)) {
|
||||
return (calculateInnerHeight() + cell.getPaddingBottom()) - getRowSpanAdaption();
|
||||
}
|
||||
}
|
||||
return cell.getRow().getHeight() - cell.getPaddingTop();
|
||||
}
|
||||
|
||||
protected abstract float calculateInnerHeight();
|
||||
|
||||
private static boolean isEqualInEpsilon(float x, float y) {
|
||||
return Math.abs(y - x) < 0.0001;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.table.HorizontalAlignment;
|
||||
import org.xbib.graphics.pdfbox.layout.table.ImageCell;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
public class ImageCellRenderer extends AbstractCellRenderer<ImageCell> {
|
||||
|
||||
public ImageCellRenderer(ImageCell cell) {
|
||||
this.cell = cell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderContent(RenderContext renderContext) {
|
||||
final PDPageContentStream contentStream = renderContext.getContentStream();
|
||||
final float moveX = renderContext.getStartingPoint().x;
|
||||
final Point2D.Float size = cell.getFitSize();
|
||||
final Point2D.Float drawAt = new Point2D.Float();
|
||||
float xOffset = moveX + cell.getPaddingLeft();
|
||||
if (cell.getSettings().getHorizontalAlignment() == HorizontalAlignment.RIGHT) {
|
||||
xOffset = moveX + (cell.getWidth() - (size.x + cell.getPaddingRight()));
|
||||
} else if (cell.getSettings().getHorizontalAlignment() == HorizontalAlignment.CENTER) {
|
||||
final float diff = (cell.getWidth() - size.x) / 2;
|
||||
xOffset = moveX + diff;
|
||||
}
|
||||
drawAt.x = xOffset;
|
||||
drawAt.y = renderContext.getStartingPoint().y + getAdaptionForVerticalAlignment() - size.y;
|
||||
try {
|
||||
contentStream.drawImage(cell.getImage(), drawAt.x, drawAt.y, size.x, size.y);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float calculateInnerHeight() {
|
||||
return (float) cell.getFitSize().getY();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class PageNotSetException extends RuntimeException {
|
||||
|
||||
public PageNotSetException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||
import org.xbib.graphics.pdfbox.layout.elements.Paragraph;
|
||||
import org.xbib.graphics.pdfbox.layout.table.HorizontalAlignment;
|
||||
import org.xbib.graphics.pdfbox.layout.table.ParagraphCell;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Alignment;
|
||||
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationDrawListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ParagraphCellRenderer extends AbstractCellRenderer<ParagraphCell> {
|
||||
|
||||
private static final Map<HorizontalAlignment, Alignment> ALIGNMENT_MAP = new EnumMap<>(HorizontalAlignment.class);
|
||||
static {
|
||||
ALIGNMENT_MAP.put(HorizontalAlignment.LEFT, Alignment.LEFT);
|
||||
ALIGNMENT_MAP.put(HorizontalAlignment.RIGHT, Alignment.RIGHT);
|
||||
ALIGNMENT_MAP.put(HorizontalAlignment.CENTER, Alignment.CENTER);
|
||||
ALIGNMENT_MAP.put(HorizontalAlignment.JUSTIFY, Alignment.JUSTIFY);
|
||||
}
|
||||
|
||||
public ParagraphCellRenderer(ParagraphCell cell) {
|
||||
this.cell = cell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderContent(RenderContext renderContext) {
|
||||
if (renderContext.getPage() == null) {
|
||||
throw new PageNotSetException("Page is not set in drawing context. Please ensure the page is set on table drawer.");
|
||||
}
|
||||
Paragraph paragraph = cell.getParagraph().getWrappedParagraph();
|
||||
AnnotationDrawListener annotationDrawListener = createAndGetAnnotationDrawListenerWith(renderContext);
|
||||
float x = renderContext.getStartingPoint().x + cell.getPaddingLeft();
|
||||
float y = renderContext.getStartingPoint().y + getAdaptionForVerticalAlignment();
|
||||
paragraph.drawText(renderContext.getContentStream(),
|
||||
new Position(x, y),
|
||||
ALIGNMENT_MAP.getOrDefault(cell.getSettings().getHorizontalAlignment(), Alignment.LEFT),
|
||||
annotationDrawListener
|
||||
);
|
||||
annotationDrawListener.afterPage(null);
|
||||
annotationDrawListener.afterRender();
|
||||
try {
|
||||
renderContext.getPage().getAnnotations().forEach(PDAnnotation::constructAppearances);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float calculateInnerHeight() {
|
||||
return cell.getParagraph().getWrappedParagraph().getHeight();
|
||||
}
|
||||
|
||||
private AnnotationDrawListener createAndGetAnnotationDrawListenerWith(RenderContext renderContext) {
|
||||
return new AnnotationDrawListener(new DrawContext() {
|
||||
@Override
|
||||
public PDDocument getPdDocument() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDPage getCurrentPage() {
|
||||
return renderContext.getPage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PDPageContentStream getCurrentPageContentStream() {
|
||||
return renderContext.getContentStream();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.table.BorderStyleInterface;
|
||||
import java.awt.Color;
|
||||
|
||||
public class PositionedLine {
|
||||
|
||||
private float width;
|
||||
|
||||
private float startX;
|
||||
|
||||
private float startY;
|
||||
|
||||
private float endX;
|
||||
|
||||
private float endY;
|
||||
|
||||
private Color color;
|
||||
|
||||
private Color resetColor;
|
||||
|
||||
private BorderStyleInterface borderStyle;
|
||||
|
||||
public void setWidth(float width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setStartX(float startX) {
|
||||
this.startX = startX;
|
||||
}
|
||||
|
||||
public float getStartX() {
|
||||
return startX;
|
||||
}
|
||||
|
||||
public void setStartY(float startY) {
|
||||
this.startY = startY;
|
||||
}
|
||||
|
||||
public float getStartY() {
|
||||
return startY;
|
||||
}
|
||||
|
||||
public void setEndX(float endX) {
|
||||
this.endX = endX;
|
||||
}
|
||||
|
||||
public float getEndX() {
|
||||
return endX;
|
||||
}
|
||||
|
||||
public void setEndY(float endY) {
|
||||
this.endY = endY;
|
||||
}
|
||||
|
||||
public float getEndY() {
|
||||
return endY;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setResetColor(Color resetColor) {
|
||||
this.resetColor = resetColor;
|
||||
}
|
||||
|
||||
public Color getResetColor() {
|
||||
return resetColor;
|
||||
}
|
||||
|
||||
public void setBorderStyle(BorderStyleInterface borderStyle) {
|
||||
this.borderStyle = borderStyle;
|
||||
}
|
||||
|
||||
public BorderStyleInterface getBorderStyle() {
|
||||
return borderStyle;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class PositionedRectangle {
|
||||
|
||||
private final float x;
|
||||
|
||||
private final float y;
|
||||
|
||||
private final float width;
|
||||
|
||||
private final float height;
|
||||
|
||||
private final Color color;
|
||||
|
||||
public PositionedRectangle(float x,
|
||||
float y,
|
||||
float width,
|
||||
float height,
|
||||
Color color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public float getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public float getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class PositionedStyledText {
|
||||
|
||||
private final float x;
|
||||
|
||||
private final float y;
|
||||
|
||||
private final String text;
|
||||
|
||||
private final Font font;
|
||||
|
||||
private final int fontSize;
|
||||
|
||||
private final Color color;
|
||||
|
||||
public PositionedStyledText(float x, float y, String text, Font font, int fontSize, Color color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.text = text;
|
||||
this.font = font;
|
||||
this.fontSize = fontSize;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public int getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
public class RenderContext {
|
||||
|
||||
private final PDPageContentStream contentStream;
|
||||
|
||||
private final PDPage page;
|
||||
|
||||
private final Point2D.Float startingPoint;
|
||||
|
||||
public RenderContext(PDPageContentStream contentStream, PDPage page, Point2D.Float startingPoint) {
|
||||
this.contentStream = contentStream;
|
||||
this.page = page;
|
||||
this.startingPoint = startingPoint;
|
||||
}
|
||||
|
||||
public PDPageContentStream getContentStream() {
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
public PDPage getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public Point2D.Float getStartingPoint() {
|
||||
return startingPoint;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import static org.xbib.graphics.pdfbox.layout.table.BorderStyle.SOLID;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
public class RenderUtil {
|
||||
|
||||
private RenderUtil() {
|
||||
}
|
||||
|
||||
public static void drawText(PDPageContentStream contentStream, PositionedStyledText styledText) {
|
||||
try {
|
||||
contentStream.beginText();
|
||||
contentStream.setNonStrokingColor(styledText.getColor());
|
||||
// TODO select correct font
|
||||
contentStream.setFont(styledText.getFont().getRegularFont(), styledText.getFontSize());
|
||||
contentStream.newLineAtOffset(styledText.getX(), styledText.getY());
|
||||
contentStream.showText(styledText.getText());
|
||||
contentStream.endText();
|
||||
contentStream.setCharacterSpacing(0);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawLine(PDPageContentStream contentStream, PositionedLine line) {
|
||||
try {
|
||||
contentStream.moveTo(line.getStartX(), line.getStartY());
|
||||
contentStream.setLineWidth(line.getWidth());
|
||||
contentStream.lineTo(line.getEndX(), line.getEndY());
|
||||
contentStream.setStrokingColor(line.getColor());
|
||||
contentStream.setLineDashPattern(line.getBorderStyle().getPattern(), line.getBorderStyle().getPhase());
|
||||
contentStream.stroke();
|
||||
contentStream.setStrokingColor(line.getResetColor());
|
||||
contentStream.setLineDashPattern(SOLID.getPattern(), SOLID.getPhase());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawRectangle(PDPageContentStream contentStream, PositionedRectangle rectangle) {
|
||||
try {
|
||||
contentStream.setNonStrokingColor(rectangle.getColor());
|
||||
contentStream.addRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
|
||||
contentStream.fill();
|
||||
contentStream.setNonStrokingColor(Color.BLACK);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
public interface Renderer {
|
||||
|
||||
void renderContent(RenderContext renderContext);
|
||||
|
||||
void renderBackground(RenderContext renderContext);
|
||||
|
||||
void renderBorders(RenderContext renderContext);
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import static org.xbib.graphics.pdfbox.layout.table.HorizontalAlignment.CENTER;
|
||||
import static org.xbib.graphics.pdfbox.layout.table.HorizontalAlignment.JUSTIFY;
|
||||
import static org.xbib.graphics.pdfbox.layout.table.HorizontalAlignment.RIGHT;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.table.AbstractTextCell;
|
||||
import org.xbib.graphics.pdfbox.layout.table.PdfUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class TextCellRenderer<T extends AbstractTextCell> extends AbstractCellRenderer<AbstractTextCell> {
|
||||
|
||||
public TextCellRenderer(T cell) {
|
||||
this.cell = cell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderContent(RenderContext renderContext) {
|
||||
float startX = renderContext.getStartingPoint().x;
|
||||
Font currentFont = cell.getFont();
|
||||
int currentFontSize = cell.getFontSize();
|
||||
Color currentTextColor = cell.getTextColor();
|
||||
float yOffset = renderContext.getStartingPoint().y + getAdaptionForVerticalAlignment();
|
||||
float xOffset = startX + cell.getPaddingLeft();
|
||||
final List<String> lines = calculateAndGetLines(currentFont, currentFontSize, cell.getMaxWidth());
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
final String line = lines.get(i);
|
||||
yOffset -= calculateYOffset(currentFont, currentFontSize, i);
|
||||
final float textWidth = PdfUtil.getStringWidth(line, currentFont, currentFontSize);
|
||||
if (cell.isHorizontallyAligned(RIGHT)) {
|
||||
xOffset = startX + (cell.getWidth() - (textWidth + cell.getPaddingRight()));
|
||||
} else if (cell.isHorizontallyAligned(CENTER)) {
|
||||
xOffset = startX + (cell.getWidth() - textWidth) / 2;
|
||||
} else if (cell.isHorizontallyAligned(JUSTIFY) && isNotLastLine(lines, i)) {
|
||||
try {
|
||||
renderContext.getContentStream().setCharacterSpacing(calculateCharSpacingFor(line));
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
PositionedStyledText text = new PositionedStyledText(xOffset, yOffset,
|
||||
line, currentFont, currentFontSize, currentTextColor);
|
||||
drawText(renderContext, text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float calculateInnerHeight() {
|
||||
return cell.getTextHeight();
|
||||
}
|
||||
|
||||
|
||||
private float calculateYOffset(Font currentFont, int currentFontSize, int lineIndex) {
|
||||
return PdfUtil.getFontHeight(currentFont, currentFontSize) +
|
||||
(lineIndex > 0 ? PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing() : 0f);
|
||||
}
|
||||
|
||||
static boolean isNotLastLine(List<String> lines, int i) {
|
||||
return i != lines.size() - 1;
|
||||
}
|
||||
|
||||
protected float calculateCharSpacingFor(String line) {
|
||||
float charSpacing = 0;
|
||||
if (line.length() > 1) {
|
||||
float size = PdfUtil.getStringWidth(line, cell.getFont(), cell.getFontSize());
|
||||
float free = cell.getWidthOfText() - size;
|
||||
if (free > 0) {
|
||||
charSpacing = free / (line.length() - 1);
|
||||
}
|
||||
}
|
||||
return charSpacing;
|
||||
}
|
||||
|
||||
protected List<String> calculateAndGetLines(Font currentFont, int currentFontSize, float maxWidth) {
|
||||
return cell.isWordBreak()
|
||||
? PdfUtil.getOptimalTextBreakLines(cell.getText(), currentFont, currentFontSize, maxWidth)
|
||||
: Collections.singletonList(cell.getText());
|
||||
}
|
||||
|
||||
protected void drawText(RenderContext renderContext, PositionedStyledText positionedStyledText) {
|
||||
RenderUtil.drawText(renderContext.getContentStream(), positionedStyledText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package org.xbib.graphics.pdfbox.layout.table.render;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.table.PdfUtil;
|
||||
import org.xbib.graphics.pdfbox.layout.table.VerticalTextCell;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Allows vertical text drawing. Note that this class is still not fully
|
||||
* developed, e.g. there is no support for text alignment settings yet.
|
||||
*/
|
||||
public class VerticalTextCellRenderer extends AbstractCellRenderer<VerticalTextCell> {
|
||||
|
||||
public VerticalTextCellRenderer(VerticalTextCell cell) {
|
||||
this.cell = cell;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderContent(RenderContext renderContext) {
|
||||
final float startX = renderContext.getStartingPoint().x;
|
||||
final float startY = renderContext.getStartingPoint().y;
|
||||
final Font currentFont = cell.getFont();
|
||||
final int currentFontSize = cell.getFontSize();
|
||||
final Color currentTextColor = cell.getTextColor();
|
||||
float yOffset = startY + cell.getPaddingBottom();
|
||||
float height = cell.getRow().getHeight();
|
||||
if (cell.getRowSpan() > 1) {
|
||||
float rowSpanAdaption = cell.calculateHeightForRowSpan() - cell.getRow().getHeight();
|
||||
yOffset -= rowSpanAdaption;
|
||||
height = cell.calculateHeightForRowSpan();
|
||||
}
|
||||
final List<String> lines = cell.isWordBreak()
|
||||
? PdfUtil.getOptimalTextBreakLines(cell.getText(), currentFont, currentFontSize, (height - cell.getVerticalPadding()))
|
||||
: Collections.singletonList(cell.getText());
|
||||
float xOffset = startX + cell.getPaddingLeft() - PdfUtil.getFontHeight(currentFont, currentFontSize);
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
final String line = lines.get(i);
|
||||
xOffset += (PdfUtil.getFontHeight(currentFont, currentFontSize) + (i > 0 ? PdfUtil.getFontHeight(currentFont, currentFontSize) * cell.getLineSpacing() : 0f));
|
||||
drawText(line, currentFont, currentFontSize, currentTextColor, xOffset, yOffset, renderContext.getContentStream());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is currently not used
|
||||
@Override
|
||||
protected float calculateInnerHeight() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void drawText(String text, Font font, int fontSize, Color color, float x, float y, PDPageContentStream contentStream) {
|
||||
try {
|
||||
// Rotate by 90 degrees counter clockwise
|
||||
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
|
||||
transform.concatenate(AffineTransform.getRotateInstance(Math.PI * 0.5));
|
||||
transform.concatenate(AffineTransform.getTranslateInstance(-x, -y - fontSize));
|
||||
contentStream.moveTo(x, y);
|
||||
contentStream.beginText();
|
||||
contentStream.setTextMatrix(new Matrix(transform));
|
||||
contentStream.setNonStrokingColor(color);
|
||||
// TODO select correct font
|
||||
contentStream.setFont(font.getRegularFont(), fontSize);
|
||||
contentStream.newLineAtOffset(x, y);
|
||||
contentStream.showText(text);
|
||||
contentStream.endText();
|
||||
contentStream.setCharacterSpacing(0);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Defines an area with a width and height.
|
||||
*/
|
||||
|
@ -9,13 +7,11 @@ public interface Area {
|
|||
|
||||
/**
|
||||
* @return the width of the area.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
float getWidth() throws IOException;
|
||||
float getWidth();
|
||||
|
||||
/**
|
||||
* @return the height of the area.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
float getHeight() throws IOException;
|
||||
float getHeight();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||
import org.xbib.graphics.pdfbox.layout.font.BaseFont;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A control fragment has no drawable representation but is meant to control the
|
||||
|
@ -11,21 +10,23 @@ import java.io.IOException;
|
|||
*/
|
||||
public class ControlFragment implements TextFragment {
|
||||
|
||||
protected final static FontDescriptor DEFAULT_FONT_DESCRIPTOR = new FontDescriptor(
|
||||
PDType1Font.HELVETICA, 11);
|
||||
protected final static FontDescriptor DEFAULT_FONT_DESCRIPTOR =
|
||||
new FontDescriptor(BaseFont.HELVETICA, 11);
|
||||
|
||||
private String name;
|
||||
|
||||
private final String text;
|
||||
|
||||
private final FontDescriptor fontDescriptor;
|
||||
|
||||
private final Color color;
|
||||
|
||||
protected ControlFragment(final String text,
|
||||
final FontDescriptor fontDescriptor) {
|
||||
protected ControlFragment(String text, FontDescriptor fontDescriptor) {
|
||||
this(null, text, fontDescriptor, Color.black);
|
||||
}
|
||||
|
||||
protected ControlFragment(final String name, final String text,
|
||||
final FontDescriptor fontDescriptor, final Color color) {
|
||||
protected ControlFragment(String name, final String text,
|
||||
FontDescriptor fontDescriptor, final Color color) {
|
||||
this.name = name;
|
||||
if (this.name == null) {
|
||||
this.name = getClass().getSimpleName();
|
||||
|
@ -36,12 +37,12 @@ public class ControlFragment implements TextFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getWidth() throws IOException {
|
||||
public float getWidth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight() throws IOException {
|
||||
public float getHeight() {
|
||||
return getFontDescriptor() == null ? 0 : getFontDescriptor().getSize();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Control fragment that represents a indent in text.
|
||||
|
@ -24,12 +24,9 @@ public class Indent extends ControlFragment {
|
|||
*
|
||||
* @param indentWidth the indentation.
|
||||
* @param indentUnit the indentation unit.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public Indent(final float indentWidth, final SpaceUnit indentUnit)
|
||||
throws IOException {
|
||||
this("", indentWidth, indentUnit, DEFAULT_FONT_DESCRIPTOR,
|
||||
Alignment.LEFT, Color.black);
|
||||
public Indent(float indentWidth, SpaceUnit indentUnit) {
|
||||
this("", indentWidth, indentUnit, DEFAULT_FONT_DESCRIPTOR, Alignment.LEFT, Color.black);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,16 +37,10 @@ public class Indent extends ControlFragment {
|
|||
* @param label the label of the indentation.
|
||||
* @param indentWidth the indentation.
|
||||
* @param indentUnit the indentation unit.
|
||||
* @param fontSize the font size, resp. the height of the new line.
|
||||
* @param font the font to use.
|
||||
* @throws IOException by pdfbox
|
||||
* @param descriptor the font size, resp. the height of the new line, the font to use.
|
||||
*/
|
||||
public Indent(final String label, final float indentWidth,
|
||||
final SpaceUnit indentUnit, final float fontSize, final PDFont font)
|
||||
throws IOException {
|
||||
|
||||
this(label, indentWidth, indentUnit, fontSize, font, Alignment.LEFT,
|
||||
Color.black);
|
||||
public Indent(String label, float indentWidth, SpaceUnit indentUnit, FontDescriptor descriptor) {
|
||||
this(label, indentWidth, indentUnit, descriptor, Alignment.LEFT, Color.black);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,38 +51,15 @@ public class Indent extends ControlFragment {
|
|||
* @param label the label of the indentation.
|
||||
* @param indentWidth the indentation.
|
||||
* @param indentUnit the indentation unit.
|
||||
* @param fontSize the font size, resp. the height of the new line.
|
||||
* @param font the font to use.
|
||||
* @param fontDescriptor the font size, resp. the height of the new line, the font to use.
|
||||
* @param alignment the alignment of the label.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public Indent(final String label, final float indentWidth,
|
||||
final SpaceUnit indentUnit, final float fontSize,
|
||||
final PDFont font, final Alignment alignment) throws IOException {
|
||||
this(label, indentWidth, indentUnit, fontSize, font, alignment,
|
||||
Color.black);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new line with the
|
||||
* {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given
|
||||
* height.
|
||||
*
|
||||
* @param label the label of the indentation.
|
||||
* @param indentWidth the indentation.
|
||||
* @param indentUnit the indentation unit.
|
||||
* @param fontSize the font size, resp. the height of the new line.
|
||||
* @param font the font to use.
|
||||
* @param alignment the alignment of the label.
|
||||
* @param color the color to use.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public Indent(final String label, final float indentWidth,
|
||||
final SpaceUnit indentUnit, final float fontSize,
|
||||
final PDFont font, final Alignment alignment, final Color color)
|
||||
throws IOException {
|
||||
this(label, indentWidth, indentUnit,
|
||||
new FontDescriptor(font, fontSize), alignment, color);
|
||||
public Indent(String label,
|
||||
float indentWidth,
|
||||
SpaceUnit indentUnit,
|
||||
FontDescriptor fontDescriptor,
|
||||
Alignment alignment) {
|
||||
this(label, indentWidth, indentUnit, fontDescriptor, alignment, Color.black);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,18 +71,19 @@ public class Indent extends ControlFragment {
|
|||
* @param fontDescriptor the font and size associated with this new line.
|
||||
* @param alignment the alignment of the label.
|
||||
* @param color the color to use.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public Indent(final String label, final float indentWidth,
|
||||
final SpaceUnit indentUnit, final FontDescriptor fontDescriptor,
|
||||
final Alignment alignment, final Color color) throws IOException {
|
||||
public Indent(String label,
|
||||
float indentWidth,
|
||||
SpaceUnit indentUnit,
|
||||
FontDescriptor fontDescriptor,
|
||||
Alignment alignment,
|
||||
Color color) {
|
||||
super("INDENT", label, fontDescriptor, color);
|
||||
|
||||
try {
|
||||
float indent = calculateIndent(indentWidth, indentUnit, fontDescriptor);
|
||||
float textWidth = 0;
|
||||
if (label != null && !label.isEmpty()) {
|
||||
textWidth = fontDescriptor.getSize()
|
||||
* fontDescriptor.getFont().getStringWidth(label) / 1000f;
|
||||
textWidth = fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getStringWidth(label) / 1000f;
|
||||
}
|
||||
float marginLeft = 0;
|
||||
float marginRight = 0;
|
||||
|
@ -132,8 +101,10 @@ public class Indent extends ControlFragment {
|
|||
break;
|
||||
}
|
||||
}
|
||||
styledText = new StyledText(label, getFontDescriptor(), getColor(), 0,
|
||||
marginLeft, marginRight);
|
||||
styledText = new StyledText(label, fontDescriptor, getColor(), 0, marginLeft, marginRight);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,8 +118,7 @@ public class Indent extends ControlFragment {
|
|||
indentPt, 0);
|
||||
}
|
||||
|
||||
private float calculateIndent(final float indentWidth,
|
||||
final SpaceUnit indentUnit, final FontDescriptor fontDescriptor)
|
||||
private float calculateIndent(float indentWidth, SpaceUnit indentUnit, final FontDescriptor fontDescriptor)
|
||||
throws IOException {
|
||||
if (indentWidth < 0) {
|
||||
return 0;
|
||||
|
@ -157,13 +127,10 @@ public class Indent extends ControlFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getWidth() throws IOException {
|
||||
public float getWidth() {
|
||||
return styledText.getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a styled text representation of the indent.
|
||||
*/
|
||||
public StyledText toStyledText() {
|
||||
return styledText;
|
||||
}
|
||||
|
@ -172,5 +139,4 @@ public class Indent extends ControlFragment {
|
|||
public String toString() {
|
||||
return "ControlFragment [" + getName() + ", " + styledText + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import org.xbib.graphics.pdfbox.layout.util.Enumerator;
|
||||
import org.xbib.graphics.pdfbox.layout.util.EnumeratorFactory;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -23,8 +21,7 @@ public class IndentCharacters {
|
|||
/**
|
||||
* Represent un-indentation, means effectively indent of 0.
|
||||
*/
|
||||
public static IndentCharacter UNINDENT_CHARACTER = new IndentCharacter("0",
|
||||
"0", "pt");
|
||||
public static IndentCharacter UNINDENT_CHARACTER = new IndentCharacter("0", "0", "pt");
|
||||
|
||||
/**
|
||||
* An <code>--{7em}</code> indicates an indentation of 7 characters in markup,
|
||||
|
@ -38,8 +35,9 @@ public class IndentCharacters {
|
|||
protected float indentWidth = 4;
|
||||
protected SpaceUnit indentUnit = SpaceUnit.em;
|
||||
|
||||
public IndentCharacter(final String level, final String indentWidth,
|
||||
final String indentUnit) {
|
||||
public IndentCharacter(String level,
|
||||
String indentWidth,
|
||||
String indentUnit) {
|
||||
super("INDENT", IndentCharacterFactory.TO_ESCAPE);
|
||||
try {
|
||||
this.level = level == null ? 0 : level.length() + 1;
|
||||
|
@ -78,16 +76,12 @@ public class IndentCharacters {
|
|||
* Creates the actual {@link Indent} fragment from this control
|
||||
* character.
|
||||
*
|
||||
* @param fontSize the current font size.
|
||||
* @param font the current font.
|
||||
* @param descriptor the current font size, the current font.
|
||||
* @param color the color to use.
|
||||
* @return the new Indent.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public Indent createNewIndent(final float fontSize, final PDFont font,
|
||||
final Color color) throws IOException {
|
||||
return new Indent(nextLabel(), level * indentWidth, indentUnit,
|
||||
fontSize, font, Alignment.RIGHT, color);
|
||||
public Indent createNewIndent(FontDescriptor descriptor, Color color) {
|
||||
return new Indent(nextLabel(), level * indentWidth, indentUnit, descriptor, Alignment.RIGHT, color);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,7 +124,7 @@ public class IndentCharacters {
|
|||
* markup, using <code>--</code> as the bullet. The number, the unit, bullet
|
||||
* character and the brackets are optional. Default indentation is 4
|
||||
* characters, default unit is <code>em</code> and the default bullet
|
||||
* depends on {@link CompatibilityHelper#getBulletCharacter(int)}. It can be
|
||||
* depends on {@link #getBulletCharacter(int)}. It can be
|
||||
* escaped with a backslash ('\').
|
||||
*/
|
||||
public static class ListCharacter extends IndentCharacter {
|
||||
|
@ -146,7 +140,7 @@ public class IndentCharacters {
|
|||
label += " ";
|
||||
}
|
||||
} else {
|
||||
label = CompatibilityHelper.getBulletCharacter(getLevel()) + " ";
|
||||
label = getBulletCharacter(getLevel()) + " ";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,6 +310,17 @@ public class IndentCharacters {
|
|||
public boolean patternMatchesBeginOfLine() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static String getBulletCharacter(final int level) {
|
||||
if (level % 2 == 1) {
|
||||
return System.getProperty("pdfbox.layout.bullet.odd", BULLET);
|
||||
}
|
||||
return System.getProperty("pdfbox.layout.bullet.even", DOUBLE_ANGLE);
|
||||
}
|
||||
|
||||
private static final String BULLET = "\u2022";
|
||||
|
||||
private static final String DOUBLE_ANGLE = "\u00bb";
|
||||
|
||||
}
|
||||
|
|
|
@ -8,32 +8,12 @@ import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
|||
*/
|
||||
public class NewLine extends ControlFragment {
|
||||
|
||||
/**
|
||||
* Creates a new line with the
|
||||
* {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}.
|
||||
*/
|
||||
public NewLine() {
|
||||
this(DEFAULT_FONT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new line with the
|
||||
* {@link ControlFragment#DEFAULT_FONT_DESCRIPTOR}'s font and the given
|
||||
* height.
|
||||
*
|
||||
* @param fontSize the font size, resp. the height of the new line.
|
||||
*/
|
||||
public NewLine(final float fontSize) {
|
||||
this(new FontDescriptor(DEFAULT_FONT_DESCRIPTOR.getFont(), fontSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new line with the given font descriptor.
|
||||
*
|
||||
* @param fontDescriptor the font and size associated with this new line.
|
||||
*/
|
||||
public NewLine(final FontDescriptor fontDescriptor) {
|
||||
public NewLine(FontDescriptor fontDescriptor) {
|
||||
super("\n", fontDescriptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,10 +25,9 @@ public enum SpaceUnit {
|
|||
* @return the size in pt.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public float toPt(final float size, final FontDescriptor fontDescriptor) throws IOException {
|
||||
public float toPt(float size, FontDescriptor fontDescriptor) throws IOException {
|
||||
if (this == em) {
|
||||
return fontDescriptor.getSize()
|
||||
* fontDescriptor.getFont().getAverageFontWidth() / 1000 * size;
|
||||
return fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getAverageFontWidth() / 1000 * size;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
/**
|
||||
* Base class representing drawable text styled with font, size, color etc.
|
||||
|
@ -27,51 +27,13 @@ public class StyledText implements TextFragment {
|
|||
*/
|
||||
private Float width = null;
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||
* @param size the size of the font.
|
||||
* @param font the font to use.
|
||||
*/
|
||||
public StyledText(final String text, final float size, final PDFont font) {
|
||||
this(text, size, font, Color.black);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||
* @param size the size of the font.
|
||||
* @param font the font to use.
|
||||
* @param color the color to use.
|
||||
*/
|
||||
public StyledText(final String text, final float size, final PDFont font,
|
||||
final Color color) {
|
||||
this(text, new FontDescriptor(font, size), color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||
* @param size the size of the font.
|
||||
* @param font the font to use.
|
||||
* @param color the color to use.
|
||||
* @param baselineOffset the offset of the baseline.
|
||||
*/
|
||||
public StyledText(final String text, final float size, final PDFont font,
|
||||
final Color color, final float baselineOffset) {
|
||||
this(text, new FontDescriptor(font, size), color, baselineOffset, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||
* @param fontDescriptor the font to use.
|
||||
*/
|
||||
public StyledText(final String text, final FontDescriptor fontDescriptor) {
|
||||
public StyledText(String text, FontDescriptor fontDescriptor) {
|
||||
this(text, fontDescriptor, Color.black);
|
||||
}
|
||||
|
||||
|
@ -82,8 +44,7 @@ public class StyledText implements TextFragment {
|
|||
* @param fontDescriptor the font to use.
|
||||
* @param color the color to use.
|
||||
*/
|
||||
public StyledText(final String text, final FontDescriptor fontDescriptor,
|
||||
final Color color) {
|
||||
public StyledText(String text, FontDescriptor fontDescriptor, Color color) {
|
||||
this(text, fontDescriptor, color, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
@ -97,12 +58,12 @@ public class StyledText implements TextFragment {
|
|||
* @param leftMargin the margin left to the text.
|
||||
* @param rightMargin the margin right to the text.
|
||||
*/
|
||||
public StyledText(final String text, final FontDescriptor fontDescriptor,
|
||||
final Color color, final float baselineOffset,
|
||||
final float leftMargin, final float rightMargin) {
|
||||
public StyledText(String text,
|
||||
FontDescriptor fontDescriptor,
|
||||
Color color,
|
||||
float baselineOffset, float leftMargin, float rightMargin) {
|
||||
if (text.contains("\n")) {
|
||||
throw new IllegalArgumentException(
|
||||
"StyledText must not contain line breaks, use TextFragment.LINEBREAK for that");
|
||||
throw new IllegalArgumentException("StyledText must not contain line breaks, use TextFragment.LINEBREAK for that");
|
||||
}
|
||||
if (leftMargin < 0) {
|
||||
throw new IllegalArgumentException("leftMargin must be >= 0");
|
||||
|
@ -133,11 +94,9 @@ public class StyledText implements TextFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getWidth() throws IOException {
|
||||
public float getWidth() {
|
||||
if (width == null) {
|
||||
width = getFontDescriptor().getSize()
|
||||
* getFontDescriptor().getFont().getStringWidth(getText())
|
||||
/ 1000;
|
||||
width = getWidth(getFontDescriptor(), getText());
|
||||
width += leftMargin;
|
||||
width += rightMargin;
|
||||
}
|
||||
|
@ -149,7 +108,7 @@ public class StyledText implements TextFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getHeight() throws IOException {
|
||||
public float getHeight() {
|
||||
return getFontDescriptor().getSize();
|
||||
}
|
||||
|
||||
|
@ -158,9 +117,7 @@ public class StyledText implements TextFragment {
|
|||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
public float getAsent() throws IOException {
|
||||
return getFontDescriptor().getSize()
|
||||
* getFontDescriptor().getFont().getFontDescriptor().getAscent()
|
||||
/ 1000;
|
||||
return getFontDescriptor().getSize() * getFontDescriptor().getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||
}
|
||||
|
||||
public float getBaselineOffset() {
|
||||
|
@ -206,12 +163,19 @@ public class StyledText implements TextFragment {
|
|||
return inheritAttributes(text, getLeftMargin(), getRightMargin());
|
||||
}
|
||||
|
||||
public StyledText inheritAttributes(String text, float leftMargin,
|
||||
float rightMargin) {
|
||||
public StyledText inheritAttributes(String text, float leftMargin, float rightMargin) {
|
||||
return new StyledText(text, getFontDescriptor(), getColor(),
|
||||
getBaselineOffset(), leftMargin, rightMargin);
|
||||
}
|
||||
|
||||
private static float getWidth(FontDescriptor fontDescriptor, String text) {
|
||||
try {
|
||||
return fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getStringWidth(text) / 1000;
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StyledText [text=" + text + ", fontDescriptor="
|
||||
|
@ -219,5 +183,4 @@ public class StyledText implements TextFragment {
|
|||
+ ", leftMargin=" + leftMargin + ", rightMargin=" + rightMargin
|
||||
+ ", baselineOffset=" + baselineOffset + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.xbib.graphics.pdfbox.layout.text;
|
|||
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import java.io.IOException;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
@ -68,9 +68,8 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
return (T) cache.get(key);
|
||||
}
|
||||
|
||||
public void addText(final String text, final float fontSize,
|
||||
final Font font) throws IOException {
|
||||
add(TextFlowUtil.createTextFlow(text, fontSize, font));
|
||||
public void addText(String text, float fontSize, Font font) {
|
||||
add(TextFlowUtil.createTextFlow(text, new FontDescriptor(font, fontSize)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,13 +77,20 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
*
|
||||
* @param markup the markup to add.
|
||||
* @param fontSize the font size to use.
|
||||
* @param baseFont the base font describing the bundle of
|
||||
* plain/blold/italic/bold-italic fonts.
|
||||
* @throws IOException by PDFBox
|
||||
* @param font the font
|
||||
*/
|
||||
public void addMarkup(final String markup, final float fontSize,
|
||||
final Font baseFont) throws IOException {
|
||||
add(TextFlowUtil.createTextFlowFromMarkup(markup, fontSize, baseFont));
|
||||
public void addMarkup(String markup, float fontSize, Font font) {
|
||||
add(TextFlowUtil.createTextFlowFromMarkup(markup, new FontDescriptor(font, fontSize)));
|
||||
}
|
||||
|
||||
public void addIndent(String label, float indentWidth, SpaceUnit indentUnit,
|
||||
float fontsize, Font font) {
|
||||
add(new Indent(label, indentWidth, indentUnit, new FontDescriptor(font, fontsize)));
|
||||
}
|
||||
|
||||
public void addIndent(String label, float indentWidth, SpaceUnit indentUnit,
|
||||
float fontsize, Font font, Alignment alignment) {
|
||||
add(new Indent(label, indentWidth, indentUnit, new FontDescriptor(font, fontsize), alignment));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +98,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
*
|
||||
* @param sequence the sequence to add.
|
||||
*/
|
||||
public void add(final TextSequence sequence) {
|
||||
public void add(TextSequence sequence) {
|
||||
for (TextFragment fragment : sequence) {
|
||||
add(fragment);
|
||||
}
|
||||
|
@ -103,7 +109,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
*
|
||||
* @param fragment the fragment to add.
|
||||
*/
|
||||
public void add(final TextFragment fragment) {
|
||||
public void add(TextFragment fragment) {
|
||||
text.add(fragment);
|
||||
clearCache();
|
||||
}
|
||||
|
@ -199,7 +205,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getWidth() throws IOException {
|
||||
public float getWidth() {
|
||||
Float width = getCachedValue(WIDTH, Float.class);
|
||||
if (width == null) {
|
||||
width = TextSequenceUtil.getWidth(this, getMaxWidth());
|
||||
|
@ -209,7 +215,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getHeight() throws IOException {
|
||||
public float getHeight() {
|
||||
Float height = getCachedValue(HEIGHT, Float.class);
|
||||
if (height == null) {
|
||||
height = TextSequenceUtil.getHeight(this, getMaxWidth(),
|
||||
|
@ -221,23 +227,22 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
|||
|
||||
@Override
|
||||
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
||||
Alignment alignment, DrawListener drawListener) throws IOException {
|
||||
Alignment alignment, DrawListener drawListener) {
|
||||
TextSequenceUtil.drawText(this, contentStream, upperLeft, drawListener, alignment,
|
||||
getMaxWidth(), getLineSpacing(),
|
||||
isApplyLineSpacingToFirstLine());
|
||||
}
|
||||
|
||||
public void drawTextRightAligned(PDPageContentStream contentStream,
|
||||
Position endOfFirstLine, DrawListener drawListener) throws IOException {
|
||||
Position endOfFirstLine, DrawListener drawListener) {
|
||||
drawText(contentStream, endOfFirstLine.add(-getWidth(), 0),
|
||||
Alignment.RIGHT, drawListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a copy of this text flow where all leading {@link NewLine}s are removed.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
public TextFlow removeLeadingEmptyLines() throws IOException {
|
||||
public TextFlow removeLeadingEmptyLines() {
|
||||
if (text.size() == 0 || !(text.get(0) instanceof NewLine)) {
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotatedStyledText;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotation;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationCharacters;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -17,11 +15,9 @@ import java.util.regex.Matcher;
|
|||
|
||||
public class TextFlowUtil {
|
||||
|
||||
public static TextFlow createTextFlow(String text,
|
||||
float fontSize,
|
||||
Font baseFont) throws IOException {
|
||||
public static TextFlow createTextFlow(String text, FontDescriptor descriptor) {
|
||||
final Iterable<CharSequence> parts = fromPlainText(text);
|
||||
return createTextFlow(parts, fontSize, baseFont);
|
||||
return createTextFlow(parts, descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,41 +48,33 @@ public class TextFlowUtil {
|
|||
* </pre>
|
||||
*
|
||||
* @param markup the markup text.
|
||||
* @param fontSize the font size to use.
|
||||
* @param baseFont the font.
|
||||
* @param descriptor the font size to use, and the font.
|
||||
* @return the created text flow.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static TextFlow createTextFlowFromMarkup(final String markup,
|
||||
final float fontSize,
|
||||
Font baseFont) throws IOException {
|
||||
public static TextFlow createTextFlowFromMarkup(String markup, FontDescriptor descriptor) {
|
||||
final Iterable<CharSequence> parts = fromMarkup(markup);
|
||||
return createTextFlow(parts, fontSize, baseFont);
|
||||
return createTextFlow(parts, descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually creates the text flow from the given (markup) text.
|
||||
*
|
||||
* @param parts the parts to create the text flow from.
|
||||
* @param fontSize the font size to use.
|
||||
* @param descriptor the font size to use.
|
||||
* @return the created text flow.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
protected static TextFlow createTextFlow(final Iterable<CharSequence> parts,
|
||||
final float fontSize, Font baseFont)
|
||||
throws IOException {
|
||||
protected static TextFlow createTextFlow(Iterable<CharSequence> parts, FontDescriptor descriptor) {
|
||||
final TextFlow result = new TextFlow();
|
||||
boolean bold = false;
|
||||
boolean italic = false;
|
||||
Color color = Color.black;
|
||||
ControlCharacters.MetricsControlCharacter metricsControl = null;
|
||||
Map<Class<? extends Annotation>, Annotation> annotationMap = new HashMap<Class<? extends Annotation>, Annotation>();
|
||||
Stack<IndentCharacters.IndentCharacter> indentStack = new Stack<IndentCharacters.IndentCharacter>();
|
||||
Map<Class<? extends Annotation>, Annotation> annotationMap = new HashMap<>();
|
||||
Stack<IndentCharacters.IndentCharacter> indentStack = new Stack<>();
|
||||
for (final CharSequence fragment : parts) {
|
||||
|
||||
if (fragment instanceof ControlCharacter) {
|
||||
if (fragment instanceof ControlCharacters.NewLineControlCharacter) {
|
||||
result.add(new NewLine(fontSize));
|
||||
result.add(new NewLine(descriptor));
|
||||
}
|
||||
if (fragment instanceof ControlCharacters.BoldControlCharacter) {
|
||||
bold = !bold;
|
||||
|
@ -110,7 +98,6 @@ public class TextFlowUtil {
|
|||
}
|
||||
if (fragment instanceof ControlCharacters.MetricsControlCharacter) {
|
||||
if (metricsControl != null && metricsControl.toString().equals(fragment.toString())) {
|
||||
// end marker
|
||||
metricsControl = null;
|
||||
} else {
|
||||
metricsControl = (ControlCharacters.MetricsControlCharacter) fragment;
|
||||
|
@ -119,10 +106,8 @@ public class TextFlowUtil {
|
|||
if (fragment instanceof IndentCharacters.IndentCharacter) {
|
||||
IndentCharacters.IndentCharacter currentIndent = (IndentCharacters.IndentCharacter) fragment;
|
||||
if (currentIndent.getLevel() == 0) {
|
||||
// indentation of 0 resets indent
|
||||
indentStack.clear();
|
||||
result.add(Indent.UNINDENT);
|
||||
continue;
|
||||
} else {
|
||||
IndentCharacters.IndentCharacter last = null;
|
||||
while (!indentStack.isEmpty()
|
||||
|
@ -135,26 +120,27 @@ public class TextFlowUtil {
|
|||
currentIndent = last;
|
||||
}
|
||||
indentStack.push(currentIndent);
|
||||
result.add(currentIndent.createNewIndent(fontSize,
|
||||
baseFont.getPlainFont(), color));
|
||||
FontDescriptor fontDescriptor =
|
||||
new FontDescriptor(descriptor.getFont(), descriptor.getSize(), bold, italic);
|
||||
result.add(currentIndent.createNewIndent(fontDescriptor, color));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PDFont font = getFont(bold, italic, baseFont);
|
||||
float baselineOffset = 0;
|
||||
float currentFontSize = fontSize;
|
||||
float currentFontSize = descriptor.getSize();
|
||||
if (metricsControl != null) {
|
||||
baselineOffset = metricsControl.getBaselineOffsetScale() * fontSize;
|
||||
baselineOffset = metricsControl.getBaselineOffsetScale() * descriptor.getSize();
|
||||
currentFontSize *= metricsControl.getFontScale();
|
||||
}
|
||||
FontDescriptor fontDescriptor = new FontDescriptor(descriptor.getFont(), currentFontSize, bold, italic);
|
||||
if (annotationMap.isEmpty()) {
|
||||
StyledText styledText = new StyledText(fragment.toString(),
|
||||
currentFontSize, font, color, baselineOffset);
|
||||
StyledText styledText = new StyledText(fragment.toString(), fontDescriptor,
|
||||
color, baselineOffset, 0, 0);
|
||||
result.add(styledText);
|
||||
} else {
|
||||
AnnotatedStyledText styledText = new AnnotatedStyledText(
|
||||
fragment.toString(), currentFontSize, baseFont, color, baselineOffset,
|
||||
annotationMap.values());
|
||||
AnnotatedStyledText styledText =
|
||||
new AnnotatedStyledText(fragment.toString(), fontDescriptor,
|
||||
color, baselineOffset, 0, 0, annotationMap.values());
|
||||
result.add(styledText);
|
||||
}
|
||||
}
|
||||
|
@ -162,33 +148,6 @@ public class TextFlowUtil {
|
|||
return result;
|
||||
}
|
||||
|
||||
protected static PDFont getFont(boolean bold, boolean italic,
|
||||
final PDFont plainFont, final PDFont boldFont,
|
||||
final PDFont italicFont, final PDFont boldItalicFont) {
|
||||
PDFont font = plainFont;
|
||||
if (bold && !italic) {
|
||||
font = boldFont;
|
||||
} else if (!bold && italic) {
|
||||
font = italicFont;
|
||||
} else if (bold) {
|
||||
font = boldItalicFont;
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
protected static PDFont getFont(boolean bold, boolean italic,
|
||||
Font baseFont) {
|
||||
PDFont font = baseFont.getPlainFont();
|
||||
if (bold && !italic) {
|
||||
font = baseFont.getBoldFont();
|
||||
} else if (!bold && italic) {
|
||||
font = baseFont.getItalicFont();
|
||||
} else if (bold) {
|
||||
font = baseFont.getBoldItalicFont();
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a char sequence where new-line is replaced by the corresponding
|
||||
* {@link ControlCharacter}.
|
||||
|
@ -233,25 +192,19 @@ public class TextFlowUtil {
|
|||
* @param markup the markup.
|
||||
* @return the create char sequence.
|
||||
*/
|
||||
public static Iterable<CharSequence> fromMarkup(
|
||||
final Iterable<CharSequence> markup) {
|
||||
public static Iterable<CharSequence> fromMarkup(Iterable<CharSequence> markup) {
|
||||
Iterable<CharSequence> text = markup;
|
||||
text = splitByControlCharacter(ControlCharacters.NEWLINE_FACTORY, text);
|
||||
text = splitByControlCharacter(ControlCharacters.METRICS_FACTORY, text);
|
||||
text = splitByControlCharacter(ControlCharacters.BOLD_FACTORY, text);
|
||||
text = splitByControlCharacter(ControlCharacters.ITALIC_FACTORY, text);
|
||||
text = splitByControlCharacter(ControlCharacters.COLOR_FACTORY, text);
|
||||
|
||||
for (AnnotationCharacters.AnnotationControlCharacterFactory<?> annotationControlCharacterFactory : AnnotationCharacters
|
||||
.getFactories()) {
|
||||
text = splitByControlCharacter(annotationControlCharacterFactory,
|
||||
text);
|
||||
text = splitByControlCharacter(annotationControlCharacterFactory, text);
|
||||
}
|
||||
|
||||
text = splitByControlCharacter(IndentCharacters.INDENT_FACTORY, text);
|
||||
|
||||
text = unescapeBackslash(text);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
@ -266,13 +219,12 @@ public class TextFlowUtil {
|
|||
protected static Iterable<CharSequence> splitByControlCharacter(
|
||||
ControlCharacters.ControlCharacterFactory controlCharacterFactory,
|
||||
final Iterable<CharSequence> markup) {
|
||||
List<CharSequence> result = new ArrayList<CharSequence>();
|
||||
List<CharSequence> result = new ArrayList<>();
|
||||
boolean beginOfLine = true;
|
||||
for (CharSequence current : markup) {
|
||||
if (current instanceof String) {
|
||||
String string = (String) current;
|
||||
int begin = 0;
|
||||
|
||||
if (!controlCharacterFactory.patternMatchesBeginOfLine()
|
||||
|| beginOfLine) {
|
||||
Matcher matcher = controlCharacterFactory.getPattern()
|
||||
|
@ -280,24 +232,18 @@ public class TextFlowUtil {
|
|||
while (matcher.find()) {
|
||||
String part = string.substring(begin, matcher.start());
|
||||
begin = matcher.end();
|
||||
|
||||
if (!part.isEmpty()) {
|
||||
String unescaped = controlCharacterFactory
|
||||
.unescape(part);
|
||||
String unescaped = controlCharacterFactory.unescape(part);
|
||||
result.add(unescaped);
|
||||
}
|
||||
|
||||
result.add(controlCharacterFactory
|
||||
.createControlCharacter(string, matcher, result));
|
||||
result.add(controlCharacterFactory.createControlCharacter(string, matcher, result));
|
||||
}
|
||||
}
|
||||
|
||||
if (begin < string.length()) {
|
||||
String part = string.substring(begin);
|
||||
String unescaped = controlCharacterFactory.unescape(part);
|
||||
result.add(unescaped);
|
||||
}
|
||||
|
||||
beginOfLine = false;
|
||||
} else {
|
||||
if (current instanceof ControlCharacters.NewLineControlCharacter) {
|
||||
|
@ -305,23 +251,19 @@ public class TextFlowUtil {
|
|||
}
|
||||
result.add(current);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Iterable<CharSequence> unescapeBackslash(
|
||||
final Iterable<CharSequence> chars) {
|
||||
List<CharSequence> result = new ArrayList<CharSequence>();
|
||||
private static Iterable<CharSequence> unescapeBackslash(Iterable<CharSequence> chars) {
|
||||
List<CharSequence> result = new ArrayList<>();
|
||||
for (CharSequence current : chars) {
|
||||
if (current instanceof String) {
|
||||
result.add(ControlCharacters
|
||||
.unescapeBackslash((String) current));
|
||||
result.add(ControlCharacters.unescapeBackslash((String) current));
|
||||
} else {
|
||||
result.add(current);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.apache.pdfbox.util.Matrix;
|
|||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -109,7 +110,7 @@ public class TextLine implements TextSequence {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getWidth() throws IOException {
|
||||
public float getWidth() {
|
||||
Float width = getCachedValue(WIDTH, Float.class);
|
||||
if (width == null) {
|
||||
width = 0f;
|
||||
|
@ -122,7 +123,7 @@ public class TextLine implements TextSequence {
|
|||
}
|
||||
|
||||
@Override
|
||||
public float getHeight() throws IOException {
|
||||
public float getHeight() {
|
||||
Float height = getCachedValue(HEIGHT, Float.class);
|
||||
if (height == null) {
|
||||
height = 0f;
|
||||
|
@ -136,16 +137,14 @@ public class TextLine implements TextSequence {
|
|||
|
||||
/**
|
||||
* @return the (max) ascent of this line.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
protected float getAscent() throws IOException {
|
||||
protected float getAscent() {
|
||||
Float ascent = getCachedValue(ASCENT, Float.class);
|
||||
if (ascent == null) {
|
||||
ascent = 0f;
|
||||
for (TextFragment fragment : this) {
|
||||
float currentAscent = fragment.getFontDescriptor().getSize()
|
||||
* fragment.getFontDescriptor().getFont()
|
||||
.getFontDescriptor().getAscent() / 1000;
|
||||
FontDescriptor fontDescriptor = fragment.getFontDescriptor();
|
||||
float currentAscent = fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||
ascent = Math.max(ascent, currentAscent);
|
||||
}
|
||||
setCachedValue(ASCENT, ascent);
|
||||
|
@ -155,13 +154,16 @@ public class TextLine implements TextSequence {
|
|||
|
||||
@Override
|
||||
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
||||
Alignment alignment, DrawListener drawListener) throws IOException {
|
||||
Alignment alignment, DrawListener drawListener) {
|
||||
drawAligned(contentStream, upperLeft, alignment, getWidth(), drawListener);
|
||||
}
|
||||
|
||||
public void drawAligned(PDPageContentStream contentStream, Position upperLeft,
|
||||
Alignment alignment, float availableLineWidth,
|
||||
DrawListener drawListener) throws IOException {
|
||||
public void drawAligned(PDPageContentStream contentStream,
|
||||
Position upperLeft,
|
||||
Alignment alignment,
|
||||
float availableLineWidth,
|
||||
DrawListener drawListener) {
|
||||
try {
|
||||
contentStream.saveGraphicsState();
|
||||
float x = upperLeft.getX();
|
||||
float y = upperLeft.getY() - getAscent();
|
||||
|
@ -191,7 +193,7 @@ public class TextLine implements TextSequence {
|
|||
contentStream.setTextMatrix(matrix);
|
||||
if (!styledText.getFontDescriptor().equals(lastFontDesc)) {
|
||||
lastFontDesc = styledText.getFontDescriptor();
|
||||
contentStream.setFont(lastFontDesc.getFont(), lastFontDesc.getSize());
|
||||
contentStream.setFont(lastFontDesc.getSelectedFont(), lastFontDesc.getSize());
|
||||
}
|
||||
if (!styledText.getColor().equals(lastColor)) {
|
||||
lastColor = styledText.getColor();
|
||||
|
@ -202,9 +204,8 @@ public class TextLine implements TextSequence {
|
|||
}
|
||||
contentStream.endText();
|
||||
if (drawListener != null) {
|
||||
float currentUpperLeft = y + styledText.getAsent();
|
||||
drawListener.drawn(styledText,
|
||||
new Position(x, currentUpperLeft),
|
||||
new Position(x, y + styledText.getAsent()),
|
||||
styledText.getWidthWithoutMargin(),
|
||||
styledText.getHeight());
|
||||
}
|
||||
|
@ -215,6 +216,9 @@ public class TextLine implements TextSequence {
|
|||
}
|
||||
}
|
||||
contentStream.restoreGraphicsState();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -7,6 +7,7 @@ import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
|||
import org.xbib.graphics.pdfbox.layout.util.Pair;
|
||||
import org.xbib.graphics.pdfbox.layout.util.WordBreakerFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -20,21 +21,16 @@ public class TextSequenceUtil {
|
|||
*
|
||||
* @param text the text to extract the lines from.
|
||||
* @return the list of text lines.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static List<TextLine> getLines(final TextSequence text)
|
||||
throws IOException {
|
||||
final List<TextLine> result = new ArrayList<TextLine>();
|
||||
|
||||
public static List<TextLine> getLines(final TextSequence text) {
|
||||
final List<TextLine> result = new ArrayList<>();
|
||||
TextLine line = new TextLine();
|
||||
for (TextFragment fragment : text) {
|
||||
if (fragment instanceof NewLine) {
|
||||
line.setNewLine((NewLine) fragment);
|
||||
result.add(line);
|
||||
line = new TextLine();
|
||||
} else if (fragment instanceof ReplacedWhitespace) {
|
||||
// ignore replaced whitespace
|
||||
} else {
|
||||
} else if (!(fragment instanceof ReplacedWhitespace)) {
|
||||
line.add((StyledText) fragment);
|
||||
}
|
||||
}
|
||||
|
@ -51,13 +47,11 @@ public class TextSequenceUtil {
|
|||
* @param maxWidth the max width used for word-wrapping.
|
||||
* @param maxHeight the max height for divide.
|
||||
* @return the Divided element containing the parts.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static Divided divide(final TextSequence text, final float maxWidth,
|
||||
final float maxHeight) throws IOException {
|
||||
final float maxHeight) {
|
||||
TextFlow wrapped = wordWrap(text, maxWidth);
|
||||
List<TextLine> lines = getLines(wrapped);
|
||||
|
||||
Paragraph first = new Paragraph();
|
||||
Paragraph tail = new Paragraph();
|
||||
if (text instanceof TextFlow) {
|
||||
|
@ -74,24 +68,19 @@ public class TextSequenceUtil {
|
|||
tail.setAlignment(paragraph.getAlignment());
|
||||
tail.setApplyLineSpacingToFirstLine(paragraph.isApplyLineSpacingToFirstLine());
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
do {
|
||||
TextLine line = lines.get(index);
|
||||
first.add(line);
|
||||
++index;
|
||||
} while (index < lines.size() && first.getHeight() < maxHeight);
|
||||
|
||||
if (first.getHeight() > maxHeight) {
|
||||
// remove last line
|
||||
--index;
|
||||
TextLine line = lines.get(index);
|
||||
for (@SuppressWarnings("unused")
|
||||
TextFragment textFragment : line) {
|
||||
for (TextFragment textFragment : line) {
|
||||
first.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = index; i < lines.size(); ++i) {
|
||||
tail.add(lines.get(i));
|
||||
}
|
||||
|
@ -104,11 +93,8 @@ public class TextSequenceUtil {
|
|||
* @param text the text to word-wrap.
|
||||
* @param maxWidth the max width to fit.
|
||||
* @return the word-wrapped text.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static TextFlow wordWrap(final TextSequence text,
|
||||
final float maxWidth) throws IOException {
|
||||
|
||||
public static TextFlow wordWrap(TextSequence text, float maxWidth) {
|
||||
float indentation = 0;
|
||||
TextFlow result = new TextFlow();
|
||||
float lineLength = indentation;
|
||||
|
@ -149,7 +135,7 @@ public class TextSequenceUtil {
|
|||
}
|
||||
|
||||
private static WordWrapContext wordWrap(final WordWrapContext context,
|
||||
final float maxWidth, final TextFlow result) throws IOException {
|
||||
final float maxWidth, final TextFlow result) {
|
||||
TextFragment word = context.getWord();
|
||||
TextFragment moreToWrap = null;
|
||||
float indentation = context.getIndentation();
|
||||
|
@ -270,9 +256,8 @@ public class TextSequenceUtil {
|
|||
*
|
||||
* @param text the text to de-wrap.
|
||||
* @return the de-wrapped text.
|
||||
* @throws IOException by PDFBox
|
||||
*/
|
||||
public static TextFlow deWrap(final TextSequence text) throws IOException {
|
||||
public static TextFlow deWrap(final TextSequence text) {
|
||||
TextFlow result = new TextFlow();
|
||||
for (TextFragment fragment : text) {
|
||||
if (fragment instanceof WrappingNewLine) {
|
||||
|
@ -297,13 +282,10 @@ public class TextSequenceUtil {
|
|||
* @param text the text to word-wrap.
|
||||
* @param maxWidth the max width to fit.
|
||||
* @return the word-wrapped text lines.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static List<TextLine> wordWrapToLines(final TextSequence text,
|
||||
final float maxWidth) throws IOException {
|
||||
TextFlow wrapped = wordWrap(text, maxWidth);
|
||||
List<TextLine> lines = getLines(wrapped);
|
||||
return lines;
|
||||
final float maxWidth) {
|
||||
return getLines(wordWrap(text, maxWidth));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -367,7 +349,7 @@ public class TextSequenceUtil {
|
|||
|
||||
private static Pair<TextFragment> breakWord(TextFragment word,
|
||||
float wordWidth, final float remainingLineWidth, float maxWidth,
|
||||
boolean breakHard) throws IOException {
|
||||
boolean breakHard) {
|
||||
|
||||
float leftMargin = 0;
|
||||
float rightMargin = 0;
|
||||
|
@ -377,8 +359,7 @@ public class TextSequenceUtil {
|
|||
rightMargin = styledText.getRightMargin();
|
||||
}
|
||||
|
||||
Pair<String> brokenWord = WordBreakerFactory.getWorkBreaker()
|
||||
.breakWord(word.getText(), word.getFontDescriptor(),
|
||||
Pair<String> brokenWord = WordBreakerFactory.getWorkBreaker().breakWord(word.getText(), word.getFontDescriptor(),
|
||||
remainingLineWidth - leftMargin, breakHard);
|
||||
if (brokenWord == null) {
|
||||
return null;
|
||||
|
@ -398,10 +379,8 @@ public class TextSequenceUtil {
|
|||
*
|
||||
* @param fontDescriptor font and size.
|
||||
* @return the width of <code>M</code>.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static float getEmWidth(final FontDescriptor fontDescriptor)
|
||||
throws IOException {
|
||||
public static float getEmWidth(final FontDescriptor fontDescriptor) {
|
||||
return getStringWidth("M", fontDescriptor);
|
||||
}
|
||||
|
||||
|
@ -411,12 +390,13 @@ public class TextSequenceUtil {
|
|||
* @param text the text to measure.
|
||||
* @param fontDescriptor font and size.
|
||||
* @return the width of given text.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static float getStringWidth(final String text,
|
||||
final FontDescriptor fontDescriptor) throws IOException {
|
||||
return fontDescriptor.getSize()
|
||||
* fontDescriptor.getFont().getStringWidth(text) / 1000;
|
||||
public static float getStringWidth(String text, FontDescriptor fontDescriptor) {
|
||||
try {
|
||||
return fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getStringWidth(text) / 1000;
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -434,13 +414,13 @@ public class TextSequenceUtil {
|
|||
* @param lineSpacing the line spacing factor.
|
||||
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
||||
* line also. Makes sense in most cases to do so.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static void drawText(TextSequence text,
|
||||
PDPageContentStream contentStream, Position upperLeft,
|
||||
DrawListener drawListener, Alignment alignment, float maxWidth,
|
||||
final float lineSpacing, final boolean applyLineSpacingToFirstLine)
|
||||
throws IOException {
|
||||
PDPageContentStream contentStream,
|
||||
Position upperLeft,
|
||||
DrawListener drawListener,
|
||||
Alignment alignment,
|
||||
float maxWidth, final float lineSpacing, final boolean applyLineSpacingToFirstLine) {
|
||||
List<TextLine> lines = wordWrapToLines(text, maxWidth);
|
||||
float maxLineWidth = Math.max(maxWidth, getMaxWidth(lines));
|
||||
Position position = upperLeft;
|
||||
|
@ -468,11 +448,9 @@ public class TextSequenceUtil {
|
|||
* @param targetWidth the target width
|
||||
* @param alignment the alignment of the line.
|
||||
* @return the left offset.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static float getOffset(final TextSequence textLine,
|
||||
final float targetWidth, final Alignment alignment)
|
||||
throws IOException {
|
||||
final float targetWidth, final Alignment alignment) {
|
||||
switch (alignment) {
|
||||
case RIGHT:
|
||||
return targetWidth - textLine.getWidth();
|
||||
|
@ -488,10 +466,8 @@ public class TextSequenceUtil {
|
|||
*
|
||||
* @param lines the lines for which to calculate the max width.
|
||||
* @return the max width of the lines.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
public static float getMaxWidth(final Iterable<TextLine> lines)
|
||||
throws IOException {
|
||||
public static float getMaxWidth(Iterable<TextLine> lines) {
|
||||
float max = 0;
|
||||
for (TextLine line : lines) {
|
||||
max = Math.max(max, line.getWidth());
|
||||
|
@ -505,10 +481,8 @@ public class TextSequenceUtil {
|
|||
* @param textSequence the text.
|
||||
* @param maxWidth if > 0, the text may be word-wrapped to match the width.
|
||||
* @return the width of the text.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
public static float getWidth(final TextSequence textSequence,
|
||||
final float maxWidth) throws IOException {
|
||||
public static float getWidth(TextSequence textSequence, float maxWidth) {
|
||||
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
||||
float max = 0;
|
||||
for (TextLine line : lines) {
|
||||
|
@ -526,11 +500,10 @@ public class TextSequenceUtil {
|
|||
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
||||
* line also. Makes sense in most cases to do so.
|
||||
* @return the height of the text.
|
||||
* @throws IOException by pdfbox
|
||||
*/
|
||||
public static float getHeight(final TextSequence textSequence,
|
||||
final float maxWidth, final float lineSpacing,
|
||||
final boolean applyLineSpacingToFirstLine) throws IOException {
|
||||
final boolean applyLineSpacingToFirstLine) {
|
||||
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
||||
float sum = 0;
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
|
@ -546,9 +519,13 @@ public class TextSequenceUtil {
|
|||
}
|
||||
|
||||
private static class WordWrapContext {
|
||||
|
||||
private final TextFragment word;
|
||||
|
||||
private final float lineLength;
|
||||
|
||||
private final float indentation;
|
||||
|
||||
boolean isWrappedLine;
|
||||
|
||||
public WordWrapContext(TextFragment word, float lineLength,
|
||||
|
@ -578,6 +555,5 @@ public class TextSequenceUtil {
|
|||
public boolean isMoreToWrap() {
|
||||
return getWord() != null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.xbib.graphics.pdfbox.layout.text;
|
||||
|
||||
|
||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||
|
||||
/**
|
||||
|
@ -9,13 +8,6 @@ import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
|||
*/
|
||||
public class WrappingNewLine extends NewLine {
|
||||
|
||||
/**
|
||||
* See {@link NewLine#NewLine()}.
|
||||
*/
|
||||
public WrappingNewLine() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link NewLine#NewLine(FontDescriptor)}.
|
||||
*
|
||||
|
@ -24,15 +16,4 @@ public class WrappingNewLine extends NewLine {
|
|||
public WrappingNewLine(FontDescriptor fontDescriptor) {
|
||||
super(fontDescriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link NewLine#NewLine(float)}.
|
||||
*
|
||||
* @param fontSize the font size, resp. the height of the new line.
|
||||
*/
|
||||
public WrappingNewLine(float fontSize) {
|
||||
super(fontSize);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -17,23 +17,6 @@ public class AnnotatedStyledText extends StyledText implements Annotated {
|
|||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||
* @param size the size of the font.
|
||||
* @param font the font to use..
|
||||
* @param color the color to use.
|
||||
* @param baselineOffset the offset of the baseline.
|
||||
* @param annotations the annotations associated with the text.
|
||||
*/
|
||||
public AnnotatedStyledText(String text, float size, Font font,
|
||||
Color color, final float baselineOffset,
|
||||
Collection<? extends Annotation> annotations) {
|
||||
this(text, new FontDescriptor(font.getPlainFont(), size), color, baselineOffset, 0, 0, annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a styled text.
|
||||
*
|
||||
|
@ -52,8 +35,7 @@ public class AnnotatedStyledText extends StyledText implements Annotated {
|
|||
final float rightMargin,
|
||||
final float baselineOffset,
|
||||
Collection<? extends Annotation> annotations) {
|
||||
super(text, fontDescriptor, color, baselineOffset, leftMargin,
|
||||
rightMargin);
|
||||
super(text, fontDescriptor, color, baselineOffset, leftMargin, rightMargin);
|
||||
if (annotations != null) {
|
||||
this.annotations.addAll(annotations);
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.AnchorAnnota
|
|||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.UnderlineAnnotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -16,7 +16,7 @@ import java.util.regex.Pattern;
|
|||
*/
|
||||
public class AnnotationCharacters {
|
||||
|
||||
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new CopyOnWriteArrayList<>();
|
||||
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new ArrayList<>();
|
||||
|
||||
static {
|
||||
register(new HyperlinkControlCharacterFactory());
|
||||
|
@ -30,8 +30,7 @@ public class AnnotationCharacters {
|
|||
*
|
||||
* @param factory the factory to register.
|
||||
*/
|
||||
public static void register(
|
||||
final AnnotationControlCharacterFactory<?> factory) {
|
||||
public static void register(AnnotationControlCharacterFactory<?> factory) {
|
||||
FACTORIES.add(factory);
|
||||
}
|
||||
|
||||
|
@ -97,8 +96,7 @@ public class AnnotationCharacters {
|
|||
|
||||
@Override
|
||||
public String unescape(String text) {
|
||||
return text
|
||||
.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||
return text.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -119,8 +117,7 @@ public class AnnotationCharacters {
|
|||
@Override
|
||||
public UnderlineControlCharacter createControlCharacter(String text,
|
||||
Matcher matcher, final List<CharSequence> charactersSoFar) {
|
||||
return new UnderlineControlCharacter(matcher.group(4),
|
||||
matcher.group(6));
|
||||
return new UnderlineControlCharacter(matcher.group(4), matcher.group(6));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -130,8 +127,7 @@ public class AnnotationCharacters {
|
|||
|
||||
@Override
|
||||
public String unescape(String text) {
|
||||
return text
|
||||
.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||
return text.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -149,6 +145,7 @@ public class AnnotationCharacters {
|
|||
*/
|
||||
public static class HyperlinkControlCharacter extends
|
||||
AnnotationControlCharacter<HyperlinkAnnotation> {
|
||||
|
||||
private HyperlinkAnnotation hyperlink;
|
||||
|
||||
protected HyperlinkControlCharacter(final String hyperlink,
|
||||
|
|
|
@ -9,7 +9,6 @@ import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
|||
import org.xbib.graphics.pdfbox.layout.text.DrawListener;
|
||||
import org.xbib.graphics.pdfbox.layout.text.DrawableText;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* This listener has to be passed to all
|
||||
|
@ -44,59 +43,35 @@ public class AnnotationDrawListener implements DrawListener, RenderListener {
|
|||
return;
|
||||
}
|
||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||
try {
|
||||
annotationProcessor.annotatedObjectDrawn(
|
||||
(Annotated) drawnObject, drawContext, upperLeft, width,
|
||||
height);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"exception on annotation processing", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException by pdfbox.
|
||||
* @deprecated user {@link #afterRender()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void finalizeAnnotations() throws IOException {
|
||||
public void finalizeAnnotations() {
|
||||
afterRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforePage(RenderContext renderContext) throws IOException {
|
||||
public void beforePage(RenderContext renderContext) {
|
||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||
try {
|
||||
annotationProcessor.beforePage(drawContext);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"exception on annotation processing", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPage(RenderContext renderContext) throws IOException {
|
||||
public void afterPage(RenderContext renderContext) {
|
||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||
try {
|
||||
annotationProcessor.afterPage(drawContext);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"exception on annotation processing", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void afterRender() throws IOException {
|
||||
public void afterRender() {
|
||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||
try {
|
||||
annotationProcessor.afterRender(drawContext.getPdDocument());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"exception on annotation processing", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package org.xbib.graphics.pdfbox.layout.text.annotations;
|
|||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Processes an annotation.
|
||||
|
@ -18,34 +17,30 @@ public interface AnnotationProcessor {
|
|||
* @param upperLeft the upper left position the object has been drawn to.
|
||||
* @param width the width of the drawn object.
|
||||
* @param height the height of the drawn object.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
void annotatedObjectDrawn(final Annotated drawnObject,
|
||||
final DrawContext drawContext, Position upperLeft, float width,
|
||||
float height) throws IOException;
|
||||
float height);
|
||||
|
||||
/**
|
||||
* Called before a page is drawn.
|
||||
*
|
||||
* @param drawContext the drawing context.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
void beforePage(final DrawContext drawContext) throws IOException;
|
||||
void beforePage(final DrawContext drawContext);
|
||||
|
||||
/**
|
||||
* Called after a page is drawn.
|
||||
*
|
||||
* @param drawContext the drawing context.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
void afterPage(final DrawContext drawContext) throws IOException;
|
||||
void afterPage(final DrawContext drawContext);
|
||||
|
||||
/**
|
||||
* Called after all rendering has been performed.
|
||||
*
|
||||
* @param document the document.
|
||||
* @throws IOException by pdfbox.
|
||||
*/
|
||||
void afterRender(final PDDocument document) throws IOException;
|
||||
void afterRender(final PDDocument document);
|
||||
|
||||
}
|
||||
|
|
|
@ -3,16 +3,23 @@ package org.xbib.graphics.pdfbox.layout.text.annotations;
|
|||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
|
||||
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.AnchorAnnotation;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle;
|
||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -26,13 +33,13 @@ import java.util.Map.Entry;
|
|||
*/
|
||||
public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||
|
||||
private final Map<String, PageAnchor> anchorMap = new HashMap<String, PageAnchor>();
|
||||
private final Map<PDPage, List<Hyperlink>> linkMap = new HashMap<PDPage, List<Hyperlink>>();
|
||||
private final Map<String, PageAnchor> anchorMap = new HashMap<>();
|
||||
private final Map<PDPage, List<Hyperlink>> linkMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void annotatedObjectDrawn(Annotated drawnObject,
|
||||
DrawContext drawContext, Position upperLeft, float width,
|
||||
float height) throws IOException {
|
||||
float height) {
|
||||
|
||||
if (!(drawnObject instanceof AnnotatedStyledText)) {
|
||||
return;
|
||||
|
@ -85,26 +92,107 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void afterRender(PDDocument document) throws IOException {
|
||||
public void afterRender(PDDocument document) {
|
||||
for (Entry<PDPage, List<Hyperlink>> entry : linkMap.entrySet()) {
|
||||
PDPage page = entry.getKey();
|
||||
List<Hyperlink> links = entry.getValue();
|
||||
for (Hyperlink hyperlink : links) {
|
||||
PDAnnotationLink pdLink = null;
|
||||
PDAnnotationLink pdLink;
|
||||
if (hyperlink.getHyperlinkURI().startsWith("#")) {
|
||||
pdLink = createGotoLink(hyperlink);
|
||||
} else {
|
||||
pdLink = CompatibilityHelper.createLink(page,
|
||||
pdLink = createLink(page,
|
||||
hyperlink.getRect(), hyperlink.getColor(),
|
||||
hyperlink.getLinkStyle(),
|
||||
hyperlink.getHyperlinkURI());
|
||||
}
|
||||
try {
|
||||
page.getAnnotations().add(pdLink);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle, final String uri) {
|
||||
PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle);
|
||||
PDActionURI actionUri = new PDActionURI();
|
||||
actionUri.setURI(uri);
|
||||
pdLink.setAction(actionUri);
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
private static PDBorderStyleDictionary noBorder;
|
||||
|
||||
private static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle) {
|
||||
PDAnnotationLink pdLink = new PDAnnotationLink();
|
||||
if (linkStyle == LinkStyle.none) {
|
||||
if (noBorder == null) {
|
||||
noBorder = new PDBorderStyleDictionary();
|
||||
noBorder.setWidth(0);
|
||||
}
|
||||
return pdLink;
|
||||
}
|
||||
PDBorderStyleDictionary borderStyle = new PDBorderStyleDictionary();
|
||||
borderStyle.setStyle(PDBorderStyleDictionary.STYLE_UNDERLINE);
|
||||
pdLink.setBorderStyle(borderStyle);
|
||||
PDRectangle rotatedRect = transformToPageRotation(rect, page);
|
||||
pdLink.setRectangle(rotatedRect);
|
||||
pdLink.setColor(toPDColor(color));
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
private static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle, final PDDestination destination) {
|
||||
PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle);
|
||||
PDActionGoTo gotoAction = new PDActionGoTo();
|
||||
gotoAction.setDestination(destination);
|
||||
pdLink.setAction(gotoAction);
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
private static PDRectangle transformToPageRotation(final PDRectangle rect, final PDPage page) {
|
||||
AffineTransform transform = transformToPageRotation(page);
|
||||
if (transform == null) {
|
||||
return rect;
|
||||
}
|
||||
float[] points = new float[]{rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getUpperRightX(), rect.getUpperRightY()};
|
||||
float[] rotatedPoints = new float[4];
|
||||
transform.transform(points, 0, rotatedPoints, 0, 2);
|
||||
PDRectangle rotated = new PDRectangle();
|
||||
rotated.setLowerLeftX(rotatedPoints[0]);
|
||||
rotated.setLowerLeftY(rotatedPoints[1]);
|
||||
rotated.setUpperRightX(rotatedPoints[2]);
|
||||
rotated.setUpperRightY(rotatedPoints[3]);
|
||||
return rotated;
|
||||
}
|
||||
|
||||
private static AffineTransform transformToPageRotation(final PDPage page) {
|
||||
int pageRotation = page.getRotation();
|
||||
if (pageRotation == 0) {
|
||||
return null;
|
||||
}
|
||||
float pageWidth = page.getMediaBox().getHeight();
|
||||
float pageHeight = page.getMediaBox().getWidth();
|
||||
AffineTransform transform = new AffineTransform();
|
||||
transform.rotate(pageRotation * Math.PI / 180, pageHeight / 2,
|
||||
pageWidth / 2);
|
||||
double offset = Math.abs(pageHeight - pageWidth) / 2;
|
||||
transform.translate(-offset, offset);
|
||||
return transform;
|
||||
}
|
||||
|
||||
private static PDColor toPDColor(final Color color) {
|
||||
float[] components = {
|
||||
color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f
|
||||
};
|
||||
return new PDColor(components, PDDeviceRGB.INSTANCE);
|
||||
}
|
||||
|
||||
private PDAnnotationLink createGotoLink(Hyperlink hyperlink) {
|
||||
String anchor = hyperlink.getHyperlinkURI().substring(1);
|
||||
PageAnchor pageAnchor = anchorMap.get(anchor);
|
||||
|
@ -116,7 +204,7 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
|||
xyzDestination.setPage(pageAnchor.getPage());
|
||||
xyzDestination.setLeft((int) pageAnchor.getX());
|
||||
xyzDestination.setTop((int) pageAnchor.getY());
|
||||
return CompatibilityHelper.createLink(pageAnchor.getPage(), hyperlink.getRect(),
|
||||
return createLink(pageAnchor.getPage(), hyperlink.getRect(),
|
||||
hyperlink.getColor(), hyperlink.getLinkStyle(), xyzDestination);
|
||||
}
|
||||
|
||||
|
@ -147,7 +235,6 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
|||
public String toString() {
|
||||
return "PageAnchor [page=" + page + ", x=" + x + ", y=" + y + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Hyperlink {
|
||||
|
@ -186,7 +273,5 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
|||
+ ", hyperlinkUri=" + hyperlinkUri + ", linkStyle="
|
||||
+ linkStyle + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,11 +9,12 @@ import org.xbib.graphics.pdfbox.layout.text.StyledText;
|
|||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.UnderlineAnnotation;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This annotation processor handles the {@link UnderlineAnnotation}s, and adds
|
||||
* This annotation processor handles the {@link UnderlineAnnotation} and adds
|
||||
* the needed hyperlink metadata to the PDF document.
|
||||
*/
|
||||
public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||
|
@ -22,23 +23,20 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
|||
|
||||
@Override
|
||||
public void annotatedObjectDrawn(Annotated drawnObject,
|
||||
DrawContext drawContext, Position upperLeft, float width,
|
||||
DrawContext drawContext,
|
||||
Position upperLeft,
|
||||
float width,
|
||||
float height) {
|
||||
if (!(drawnObject instanceof StyledText)) {
|
||||
return;
|
||||
}
|
||||
StyledText drawnText = (StyledText) drawnObject;
|
||||
for (UnderlineAnnotation underlineAnnotation : drawnObject
|
||||
.getAnnotationsOfType(UnderlineAnnotation.class)) {
|
||||
for (UnderlineAnnotation underlineAnnotation : drawnObject.getAnnotationsOfType(UnderlineAnnotation.class)) {
|
||||
float fontSize = drawnText.getFontDescriptor().getSize();
|
||||
float ascent = fontSize
|
||||
* drawnText.getFontDescriptor().getFont()
|
||||
.getFontDescriptor().getAscent() / 1000;
|
||||
float ascent = fontSize * drawnText.getFontDescriptor().getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||
float baselineOffset = fontSize * underlineAnnotation.getBaselineOffsetScale();
|
||||
float thickness = (0.01f + fontSize * 0.05f)
|
||||
* underlineAnnotation.getLineWeight();
|
||||
Position start = new Position(upperLeft.getX(), upperLeft.getY()
|
||||
- ascent + baselineOffset);
|
||||
float thickness = (0.01f + fontSize * 0.05f) * underlineAnnotation.getLineWeight();
|
||||
Position start = new Position(upperLeft.getX(), upperLeft.getY() - ascent + baselineOffset);
|
||||
Position end = new Position(start.getX() + width, start.getY());
|
||||
Stroke stroke = Stroke.builder().lineWidth(thickness).build();
|
||||
Line line = new Line(start, end, stroke, drawnText.getColor());
|
||||
|
@ -47,12 +45,12 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforePage(DrawContext drawContext) throws IOException {
|
||||
public void beforePage(DrawContext drawContext) {
|
||||
linesOnPage.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPage(DrawContext drawContext) throws IOException {
|
||||
public void afterPage(DrawContext drawContext) {
|
||||
for (Line line : linesOnPage) {
|
||||
line.draw(drawContext.getCurrentPageContentStream());
|
||||
}
|
||||
|
@ -60,15 +58,18 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void afterRender(PDDocument document) throws IOException {
|
||||
public void afterRender(PDDocument document) {
|
||||
linesOnPage.clear();
|
||||
}
|
||||
|
||||
private static class Line {
|
||||
|
||||
private final Position start;
|
||||
|
||||
private final Position end;
|
||||
|
||||
private final Stroke stroke;
|
||||
|
||||
private final Color color;
|
||||
|
||||
public Line(Position start, Position end, Stroke stroke, Color color) {
|
||||
|
@ -79,7 +80,8 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
|||
this.color = color;
|
||||
}
|
||||
|
||||
public void draw(PDPageContentStream contentStream) throws IOException {
|
||||
public void draw(PDPageContentStream contentStream) {
|
||||
try {
|
||||
if (color != null) {
|
||||
contentStream.setStrokingColor(color);
|
||||
}
|
||||
|
@ -88,6 +90,9 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
|||
}
|
||||
contentStream.moveTo(start.getX(), start.getY());
|
||||
contentStream.lineTo(end.getX(), end.getY());
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
package org.xbib.graphics.pdfbox.layout.util;
|
||||
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
|
||||
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
|
||||
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
|
||||
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
|
||||
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
|
||||
import org.apache.pdfbox.util.Matrix;
|
||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle;
|
||||
import java.awt.Color;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CompatibilityHelper {
|
||||
|
||||
private static final String BULLET = "\u2022";
|
||||
|
||||
private static final String DOUBLE_ANGLE = "\u00bb";
|
||||
|
||||
private static PDBorderStyleDictionary noBorder;
|
||||
|
||||
/**
|
||||
* Returns the bullet character for the given level. Actually only two
|
||||
* bullets are used for odd and even levels. For odd levels the
|
||||
* {@link #BULLET bullet} character is used, for even it is the
|
||||
* {@link #DOUBLE_ANGLE double angle}. You may customize this by setting the
|
||||
* system properties <code>pdfbox.layout.bullet.odd</code> and/or
|
||||
* <code>pdfbox.layout.bullet.even</code>.
|
||||
*
|
||||
* @param level the level to return the bullet for.
|
||||
* @return the bullet character for the leve.
|
||||
*/
|
||||
public static String getBulletCharacter(final int level) {
|
||||
if (level % 2 == 1) {
|
||||
return System.getProperty("pdfbox.layout.bullet.odd", BULLET);
|
||||
}
|
||||
return System.getProperty("pdfbox.layout.bullet.even", DOUBLE_ANGLE);
|
||||
}
|
||||
|
||||
public static void clip(final PDPageContentStream contentStream)
|
||||
throws IOException {
|
||||
contentStream.clip();
|
||||
}
|
||||
|
||||
public static void transform(final PDPageContentStream contentStream,
|
||||
float a, float b, float c, float d, float e, float f)
|
||||
throws IOException {
|
||||
contentStream.transform(new Matrix(a, b, c, d, e, f));
|
||||
}
|
||||
|
||||
public static void curveTo(final PDPageContentStream contentStream, float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
|
||||
contentStream.curveTo(x1, y1, x2, y2, x3, y3);
|
||||
}
|
||||
|
||||
public static void curveTo1(final PDPageContentStream contentStream, float x1, float y1, float x3, float y3) throws IOException {
|
||||
contentStream.curveTo1(x1, y1, x3, y3);
|
||||
}
|
||||
|
||||
public static void fillNonZero(final PDPageContentStream contentStream) throws IOException {
|
||||
contentStream.fill();
|
||||
}
|
||||
|
||||
public static void showText(final PDPageContentStream contentStream,
|
||||
final String text) throws IOException {
|
||||
contentStream.showText(text);
|
||||
}
|
||||
|
||||
public static PDPageContentStream createAppendablePDPageContentStream(
|
||||
final PDDocument pdDocument, final PDPage page) throws IOException {
|
||||
return new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||
}
|
||||
|
||||
|
||||
public static int getPageRotation(final PDPage page) {
|
||||
return page.getRotation();
|
||||
}
|
||||
|
||||
public static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle, final String uri) {
|
||||
PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle);
|
||||
|
||||
PDActionURI actionUri = new PDActionURI();
|
||||
actionUri.setURI(uri);
|
||||
pdLink.setAction(actionUri);
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
public static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle, final PDDestination destination) {
|
||||
PDAnnotationLink pdLink = createLink(page, rect, color, linkStyle);
|
||||
PDActionGoTo gotoAction = new PDActionGoTo();
|
||||
gotoAction.setDestination(destination);
|
||||
pdLink.setAction(gotoAction);
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color in the annotation.
|
||||
*
|
||||
* @param annotation the annotation.
|
||||
* @param color the color to set.
|
||||
*/
|
||||
public static void setAnnotationColor(PDAnnotation annotation, Color color) {
|
||||
annotation.setColor(toPDColor(color));
|
||||
}
|
||||
|
||||
private static PDColor toPDColor(final Color color) {
|
||||
float[] components = {
|
||||
color.getRed() / 255f, color.getGreen() / 255f, color.getBlue() / 255f
|
||||
};
|
||||
return new PDColor(components, PDDeviceRGB.INSTANCE);
|
||||
}
|
||||
|
||||
private static PDAnnotationLink createLink(PDPage page, PDRectangle rect, Color color,
|
||||
LinkStyle linkStyle) {
|
||||
PDAnnotationLink pdLink = new PDAnnotationLink();
|
||||
if (linkStyle == LinkStyle.none) {
|
||||
if (noBorder == null) {
|
||||
noBorder = new PDBorderStyleDictionary();
|
||||
noBorder.setWidth(0);
|
||||
}
|
||||
return pdLink;
|
||||
}
|
||||
PDBorderStyleDictionary borderStyle = new PDBorderStyleDictionary();
|
||||
borderStyle.setStyle(PDBorderStyleDictionary.STYLE_UNDERLINE);
|
||||
pdLink.setBorderStyle(borderStyle);
|
||||
PDRectangle rotatedRect = transformToPageRotation(rect, page);
|
||||
pdLink.setRectangle(rotatedRect);
|
||||
setAnnotationColor(pdLink, color);
|
||||
return pdLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the quad points representation of the given rect.
|
||||
*
|
||||
* @param rect the rectangle.
|
||||
* @return the quad points.
|
||||
*/
|
||||
public static float[] toQuadPoints(final PDRectangle rect) {
|
||||
return toQuadPoints(rect, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the quad points representation of the given rect.
|
||||
*
|
||||
* @param rect the rectangle.
|
||||
* @param xOffset the offset in x-direction to add.
|
||||
* @param yOffset the offset in y-direction to add.
|
||||
* @return the quad points.
|
||||
*/
|
||||
private static float[] toQuadPoints(final PDRectangle rect, float xOffset, float yOffset) {
|
||||
float[] quads = new float[8];
|
||||
quads[0] = rect.getLowerLeftX() + xOffset; // x1
|
||||
quads[1] = rect.getUpperRightY() + yOffset; // y1
|
||||
quads[2] = rect.getUpperRightX() + xOffset; // x2
|
||||
quads[3] = quads[1]; // y2
|
||||
quads[4] = quads[0]; // x3
|
||||
quads[5] = rect.getLowerLeftY() + yOffset; // y3
|
||||
quads[6] = quads[2]; // x4
|
||||
quads[7] = quads[5]; // y5
|
||||
return quads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the quad points in order to match the page rotation
|
||||
*
|
||||
* @param quadPoints the quad points.
|
||||
* @param page the page.
|
||||
* @return the transformed quad points.
|
||||
*/
|
||||
public static float[] transformToPageRotation(final float[] quadPoints, final PDPage page) {
|
||||
AffineTransform transform = transformToPageRotation(page);
|
||||
if (transform == null) {
|
||||
return quadPoints;
|
||||
}
|
||||
float[] rotatedPoints = new float[quadPoints.length];
|
||||
transform.transform(quadPoints, 0, rotatedPoints, 0, 4);
|
||||
return rotatedPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the rectangle in order to match the page rotation
|
||||
*
|
||||
* @param rect the rectangle.
|
||||
* @param page the page.
|
||||
* @return the transformed rectangle.
|
||||
*/
|
||||
private static PDRectangle transformToPageRotation(final PDRectangle rect, final PDPage page) {
|
||||
AffineTransform transform = transformToPageRotation(page);
|
||||
if (transform == null) {
|
||||
return rect;
|
||||
}
|
||||
float[] points = new float[]{rect.getLowerLeftX(), rect.getLowerLeftY(), rect.getUpperRightX(), rect.getUpperRightY()};
|
||||
float[] rotatedPoints = new float[4];
|
||||
transform.transform(points, 0, rotatedPoints, 0, 2);
|
||||
PDRectangle rotated = new PDRectangle();
|
||||
rotated.setLowerLeftX(rotatedPoints[0]);
|
||||
rotated.setLowerLeftY(rotatedPoints[1]);
|
||||
rotated.setUpperRightX(rotatedPoints[2]);
|
||||
rotated.setUpperRightY(rotatedPoints[3]);
|
||||
return rotated;
|
||||
}
|
||||
|
||||
private static AffineTransform transformToPageRotation(final PDPage page) {
|
||||
int pageRotation = getPageRotation(page);
|
||||
if (pageRotation == 0) {
|
||||
return null;
|
||||
}
|
||||
float pageWidth = page.getMediaBox().getHeight();
|
||||
float pageHeight = page.getMediaBox().getWidth();
|
||||
AffineTransform transform = new AffineTransform();
|
||||
transform.rotate(pageRotation * Math.PI / 180, pageHeight / 2,
|
||||
pageWidth / 2);
|
||||
double offset = Math.abs(pageHeight - pageWidth) / 2;
|
||||
transform.translate(-offset, offset);
|
||||
return transform;
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ import org.xbib.graphics.pdfbox.layout.util.Enumerators.ArabicEnumerator;
|
|||
import org.xbib.graphics.pdfbox.layout.util.Enumerators.LowerCaseAlphabeticEnumerator;
|
||||
import org.xbib.graphics.pdfbox.layout.util.Enumerators.LowerCaseRomanEnumerator;
|
||||
import org.xbib.graphics.pdfbox.layout.util.Enumerators.RomanEnumerator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Enumerators are created using this factory. It allows you to register and use
|
||||
|
@ -23,7 +23,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
*/
|
||||
public class EnumeratorFactory {
|
||||
|
||||
private final static Map<String, Class<? extends Enumerator>> ENUMERATORS = new ConcurrentHashMap<String, Class<? extends Enumerator>>();
|
||||
private final static Map<String, Class<? extends Enumerator>> ENUMERATORS = new HashMap<>();
|
||||
|
||||
static {
|
||||
register("1", ArabicEnumerator.class);
|
||||
|
|
|
@ -23,6 +23,6 @@ public interface WordBreaker {
|
|||
*/
|
||||
Pair<String> breakWord(String word,
|
||||
FontDescriptor fontDescriptor, float maxWidth,
|
||||
boolean breakHardIfNecessary) throws IOException;
|
||||
boolean breakHardIfNecessary);
|
||||
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package org.xbib.graphics.pdfbox.layout.util;
|
|||
|
||||
import org.xbib.graphics.pdfbox.layout.util.WordBreakers.DefaultWordBreaker;
|
||||
import org.xbib.graphics.pdfbox.layout.util.WordBreakers.NonBreakingWordBreaker;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Factory for creating a {@link WordBreaker}. This may be used to define a
|
||||
|
@ -21,12 +21,6 @@ public class WordBreakerFactory {
|
|||
*/
|
||||
public final static String WORD_BREAKER_CLASS_PROPERTY = "pdfbox.layout.word.breaker";
|
||||
|
||||
/**
|
||||
* class name of the default word breaker.
|
||||
*/
|
||||
public final static String DEFAULT_WORD_BREAKER_CLASS_NAME = DefaultWordBreaker.class
|
||||
.getName();
|
||||
|
||||
/**
|
||||
* class name of the (legacy) non-breaking word breaker.
|
||||
*/
|
||||
|
@ -34,7 +28,8 @@ public class WordBreakerFactory {
|
|||
.getName();
|
||||
|
||||
private final static WordBreaker DEFAULT_WORD_BREAKER = new DefaultWordBreaker();
|
||||
private final static Map<String, WordBreaker> WORD_BREAKERS = new ConcurrentHashMap<String, WordBreaker>();
|
||||
|
||||
private final static Map<String, WordBreaker> WORD_BREAKERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @return the word breaker instance to use.
|
||||
|
@ -59,8 +54,7 @@ public class WordBreakerFactory {
|
|||
try {
|
||||
return (WordBreaker) Class.forName(className).getDeclaredConstructor().newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(String.format(
|
||||
"failed to create word breaker '%s'", className), e);
|
||||
throw new RuntimeException(String.format("failed to create word breaker '%s'", className), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue