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 {
|
builder.create {
|
||||||
document {
|
document {
|
||||||
paragraph {
|
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 {
|
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;
|
||||||
exports org.xbib.graphics.pdfbox.layout.elements.render;
|
exports org.xbib.graphics.pdfbox.layout.elements.render;
|
||||||
exports org.xbib.graphics.pdfbox.layout.font;
|
exports org.xbib.graphics.pdfbox.layout.font;
|
||||||
exports org.xbib.graphics.pdfbox.layout.shape;
|
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;
|
||||||
exports org.xbib.graphics.pdfbox.layout.text.annotations;
|
exports org.xbib.graphics.pdfbox.layout.text.annotations;
|
||||||
exports org.xbib.graphics.pdfbox.layout.util;
|
exports org.xbib.graphics.pdfbox.layout.util;
|
||||||
requires transitive org.xbib.graphics.barcode;
|
requires transitive org.xbib.graphics.barcode;
|
||||||
requires transitive org.xbib.graphics.pdfbox;
|
requires transitive org.xbib.graphics.pdfbox;
|
||||||
requires transitive java.desktop;
|
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,
|
private Entry<Element, LayoutHint> createEntry(final Element element,
|
||||||
final LayoutHint layoutHint) {
|
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();
|
Element element = entry.getKey();
|
||||||
LayoutHint layoutHint = entry.getValue();
|
LayoutHint layoutHint = entry.getValue();
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
Iterator<Renderer> customRendererIterator = customRenderer.iterator();
|
||||||
// first ask custom renderer to render the element
|
|
||||||
Iterator<Renderer> customRendererIterator = customRenderer
|
|
||||||
.iterator();
|
|
||||||
while (!success && customRendererIterator.hasNext()) {
|
while (!success && customRendererIterator.hasNext()) {
|
||||||
success = customRendererIterator.next().render(renderContext,
|
success = customRendererIterator.next().render(renderContext, element, layoutHint);
|
||||||
element, layoutHint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if none of them felt responsible, let the default renderer do the job.
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
success = renderContext.render(renderContext, element,
|
success = renderContext.render(renderContext, element, layoutHint);
|
||||||
layoutHint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(String.format(
|
||||||
String.format(
|
|
||||||
"neither layout %s nor the render context knows what to do with %s",
|
"neither layout %s nor the render context knows what to do with %s",
|
||||||
renderContext.getLayout(), element));
|
renderContext.getLayout(), element));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
renderContext.close();
|
renderContext.close();
|
||||||
|
|
||||||
resetPDDocument();
|
resetPDDocument();
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The frame is a container for a {@link Drawable}, that allows to add margin,
|
* 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 {
|
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 paddingLeft;
|
||||||
private float paddingRight;
|
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.TextFlow;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.TextSequenceUtil;
|
import org.xbib.graphics.pdfbox.layout.text.TextSequenceUtil;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
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
|
* 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
|
@Override
|
||||||
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||||
Position upperLeft, DrawListener drawListener) throws IOException {
|
Position upperLeft, DrawListener drawListener) {
|
||||||
drawText(contentStream, upperLeft, getAlignment(), drawListener);
|
drawText(contentStream, upperLeft, getAlignment(), drawListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Divided divide(float remainingHeight, final float pageHeight)
|
public Divided divide(float remainingHeight, final float pageHeight) {
|
||||||
throws IOException {
|
|
||||||
return TextSequenceUtil.divide(this, getMaxWidth(), remainingHeight);
|
return TextSequenceUtil.divide(this, getMaxWidth(), remainingHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Paragraph removeLeadingEmptyVerticalSpace() throws IOException {
|
public Paragraph removeLeadingEmptyVerticalSpace() {
|
||||||
return removeLeadingEmptyLines();
|
return removeLeadingEmptyLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Paragraph removeLeadingEmptyLines() throws IOException {
|
public Paragraph removeLeadingEmptyLines() {
|
||||||
Paragraph result = (Paragraph) super.removeLeadingEmptyLines();
|
Paragraph result = (Paragraph) super.removeLeadingEmptyLines();
|
||||||
result.setAbsolutePosition(this.getAbsolutePosition());
|
result.setAbsolutePosition(this.getAbsolutePosition());
|
||||||
result.setAlignment(this.getAlignment());
|
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.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
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.ControlElement;
|
||||||
import org.xbib.graphics.pdfbox.layout.elements.Document;
|
import org.xbib.graphics.pdfbox.layout.elements.Document;
|
||||||
import org.xbib.graphics.pdfbox.layout.elements.Element;
|
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.DrawListener;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationDrawListener;
|
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationDrawListener;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
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.
|
* @return <code>true</code> if the page is rotated by 90/270 degrees.
|
||||||
*/
|
*/
|
||||||
public boolean isPageTilted() {
|
public boolean isPageTilted() {
|
||||||
return CompatibilityHelper.getPageRotation(page) == 90
|
return page.getRotation() == 90 || page.getRotation() == 270;
|
||||||
|| CompatibilityHelper.getPageRotation(page) == 270;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -374,12 +373,9 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
||||||
if (nextPageFormat != null) {
|
if (nextPageFormat != null) {
|
||||||
setPageFormat(nextPageFormat);
|
setPageFormat(nextPageFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.page = new PDPage(getPageFormat().getMediaBox());
|
this.page = new PDPage(getPageFormat().getMediaBox());
|
||||||
this.pdDocument.addPage(page);
|
this.pdDocument.addPage(page);
|
||||||
this.contentStream = CompatibilityHelper
|
this.contentStream = new PDPageContentStream(pdDocument, page, PDPageContentStream.AppendMode.APPEND, true);
|
||||||
.createAppendablePDPageContentStream(pdDocument, page);
|
|
||||||
|
|
||||||
// fix orientation
|
// fix orientation
|
||||||
if (getPageOrientation() != getPageFormat().getOrientation()) {
|
if (getPageOrientation() != getPageFormat().getOrientation()) {
|
||||||
if (isPageTilted()) {
|
if (isPageTilted()) {
|
||||||
|
@ -389,7 +385,7 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPageTilted()) {
|
if (isPageTilted()) {
|
||||||
CompatibilityHelper.transform(contentStream, 0, 1, -1, 0, getPageHeight(), 0);
|
contentStream.transform(new Matrix(0, 1, -1, 0, getPageHeight(), 0));
|
||||||
}
|
}
|
||||||
resetPositionToUpperLeft();
|
resetPositionToUpperLeft();
|
||||||
resetMaxPositionOnPage();
|
resetMaxPositionOnPage();
|
||||||
|
@ -410,8 +406,7 @@ public class RenderContext implements Renderer, Closeable, DrawContext, DrawList
|
||||||
document.afterPage(this);
|
document.afterPage(this);
|
||||||
|
|
||||||
if (getPageFormat().getRotation() != 0) {
|
if (getPageFormat().getRotation() != 0) {
|
||||||
int currentRotation = CompatibilityHelper
|
int currentRotation = getCurrentPage().getRotation();
|
||||||
.getPageRotation(getCurrentPage());
|
|
||||||
getCurrentPage().setRotation(
|
getCurrentPage().setRotation(
|
||||||
currentRotation + getPageFormat().getRotation());
|
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.Alignment;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
import org.xbib.graphics.pdfbox.layout.text.WidthRespecting;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -246,17 +245,13 @@ public class VerticalLayout implements Layout {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contentStream.saveGraphicsState();
|
contentStream.saveGraphicsState();
|
||||||
contentStream.addRect(0, pageFormat.getMarginBottom(), renderContext.getPageWidth(),
|
contentStream.addRect(0, pageFormat.getMarginBottom(), renderContext.getPageWidth(),
|
||||||
renderContext.getHeight());
|
renderContext.getHeight());
|
||||||
CompatibilityHelper.clip(contentStream);
|
contentStream.clip();
|
||||||
|
|
||||||
drawable.draw(renderContext.getPdDocument(), contentStream,
|
drawable.draw(renderContext.getPdDocument(), contentStream,
|
||||||
renderContext.getCurrentPosition().add(offsetX, 0), renderContext);
|
renderContext.getCurrentPosition().add(offsetX, 0), renderContext);
|
||||||
|
|
||||||
contentStream.restoreGraphicsState();
|
contentStream.restoreGraphicsState();
|
||||||
|
|
||||||
if (movePosition) {
|
if (movePosition) {
|
||||||
renderContext.movePositionBy(0, -drawable.getHeight());
|
renderContext.movePositionBy(0, -drawable.getHeight());
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ import org.apache.pdfbox.pdmodel.font.PDType1Font;
|
||||||
public enum BaseFont implements Font {
|
public enum BaseFont implements Font {
|
||||||
|
|
||||||
TIMES(PDType1Font.TIMES_ROMAN, PDType1Font.TIMES_BOLD,
|
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,
|
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,
|
HELVETICA(PDType1Font.HELVETICA, PDType1Font.HELVETICA_BOLD,
|
||||||
PDType1Font.HELVETICA_OBLIQUE, PDType1Font.HELVETICA_BOLD_OBLIQUE);
|
PDType1Font.HELVETICA_OBLIQUE, PDType1Font.HELVETICA_BOLD_OBLIQUE);
|
||||||
|
@ -28,8 +28,7 @@ public enum BaseFont implements Font {
|
||||||
|
|
||||||
private final PDFont boldItalicFont;
|
private final PDFont boldItalicFont;
|
||||||
|
|
||||||
BaseFont(PDFont plainFont, PDFont boldFont, PDFont italicFont,
|
BaseFont(PDFont plainFont, PDFont boldFont, PDFont italicFont, PDFont boldItalicFont) {
|
||||||
PDFont boldItalicFont) {
|
|
||||||
this.plainFont = plainFont;
|
this.plainFont = plainFont;
|
||||||
this.boldFont = boldFont;
|
this.boldFont = boldFont;
|
||||||
this.italicFont = italicFont;
|
this.italicFont = italicFont;
|
||||||
|
@ -37,7 +36,7 @@ public enum BaseFont implements Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PDFont getPlainFont() {
|
public PDFont getRegularFont() {
|
||||||
return plainFont;
|
return plainFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
|
||||||
public interface Font {
|
public interface Font {
|
||||||
|
|
||||||
PDFont getPlainFont();
|
PDFont getRegularFont();
|
||||||
|
|
||||||
PDFont getBoldFont();
|
PDFont getBoldFont();
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,56 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.font;
|
package org.xbib.graphics.pdfbox.layout.font;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.apache.pdfbox.pdmodel.font.PDFont;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Container for a Font and size.
|
* Container for a Font and size.
|
||||||
*/
|
*/
|
||||||
public class FontDescriptor {
|
public class FontDescriptor {
|
||||||
|
|
||||||
/**
|
private final Font font;
|
||||||
* the associated font.
|
|
||||||
*/
|
|
||||||
private final PDFont font;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the font size.
|
|
||||||
*/
|
|
||||||
private final float size;
|
private final float size;
|
||||||
|
|
||||||
/**
|
private final boolean regular;
|
||||||
* Creates the descriptor the the given font and size.
|
|
||||||
*
|
private final boolean bold;
|
||||||
* @param font the font.
|
|
||||||
* @param size the size.
|
private final boolean italic;
|
||||||
*/
|
|
||||||
public FontDescriptor(final PDFont font, final float size) {
|
public FontDescriptor(Font font, float size) {
|
||||||
this.font = font;
|
this(font, size, false, false);
|
||||||
this.size = size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public FontDescriptor(Font font, float size, boolean bold, boolean italic) {
|
||||||
* @return the font.
|
this.font = font;
|
||||||
*/
|
this.size = size;
|
||||||
public PDFont getFont() {
|
this.regular = !bold && !italic;
|
||||||
|
this.bold = bold;
|
||||||
|
this.italic = italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Font getFont() {
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the size.
|
|
||||||
*/
|
|
||||||
public float getSize() {
|
public float getSize() {
|
||||||
return size;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "FontDescriptor [font=" + font + ", size=" + size + "]";
|
return "FontDescriptor [font=" + font + ", size=" + size + "]";
|
||||||
|
@ -49,33 +58,14 @@ public class FontDescriptor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
return Objects.hash(font, size, regular, bold, italic);
|
||||||
int result = 1;
|
|
||||||
result = prime * result + ((font == null) ? 0 : font.hashCode());
|
|
||||||
result = prime * result + Float.floatToIntBits(size);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object obj) {
|
public boolean equals(final Object obj) {
|
||||||
if (this == obj) {
|
if (!(obj instanceof FontDescriptor)) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (getClass() != obj.getClass()) {
|
return Objects.hashCode(obj) == Objects.hash(this);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
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.PDFont;
|
||||||
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
import org.apache.pdfbox.pdmodel.font.PDType0Font;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class NotoSansFont implements Font {
|
public class NotoSansFont implements Font {
|
||||||
|
|
||||||
|
private final PDDocument document;
|
||||||
|
|
||||||
private static PDType0Font regular;
|
private static PDType0Font regular;
|
||||||
|
|
||||||
private static PDType0Font bold;
|
private static PDType0Font bold;
|
||||||
|
@ -16,32 +19,47 @@ public class NotoSansFont implements Font {
|
||||||
|
|
||||||
private static PDType0Font bolditalic;
|
private static PDType0Font bolditalic;
|
||||||
|
|
||||||
public NotoSansFont(PDDocument document) throws IOException {
|
public NotoSansFont(PDDocument document) {
|
||||||
if (regular == null) {
|
this.document = document;
|
||||||
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")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PDFont getPlainFont() {
|
public PDFont getRegularFont() {
|
||||||
|
if (regular == null) {
|
||||||
|
regular = load(document, "NotoSans-Regular.ttf");
|
||||||
|
}
|
||||||
return regular;
|
return regular;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PDFont getBoldFont() {
|
public PDFont getBoldFont() {
|
||||||
|
if (bold == null) {
|
||||||
|
bold = load(document, "NotoSans-Bold.ttf");
|
||||||
|
}
|
||||||
return bold;
|
return bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PDFont getItalicFont() {
|
public PDFont getItalicFont() {
|
||||||
|
if (italic == null) {
|
||||||
|
italic = load(document, "NotoSans-Italic.ttf");
|
||||||
|
}
|
||||||
return italic;
|
return italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PDFont getBoldItalicFont() {
|
public PDFont getBoldItalicFont() {
|
||||||
|
if (bolditalic == null) {
|
||||||
|
bolditalic = load(document, "NotoSans-BoldItalic.ttf");
|
||||||
|
}
|
||||||
return bolditalic;
|
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.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.DrawListener;
|
import org.xbib.graphics.pdfbox.layout.text.DrawListener;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for shapes which performs the
|
* Abstract base class for shapes which performs the
|
||||||
* {@link #fill(PDDocument, PDPageContentStream, Position, float, float, Color, DrawListener)}
|
* {@link #fill(PDDocument, PDPageContentStream, Position, float, float, Color, DrawListener)}
|
||||||
* and (@link
|
* and {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener)}
|
||||||
* {@link #draw(PDDocument, PDPageContentStream, Position, float, float, Color, Stroke, DrawListener)}
|
|
||||||
* .
|
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractShape implements Shape {
|
public abstract class AbstractShape implements Shape {
|
||||||
|
|
||||||
|
@ -21,9 +18,7 @@ public abstract class AbstractShape implements Shape {
|
||||||
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
public void draw(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||||
Position upperLeft, float width, float height, Color color,
|
Position upperLeft, float width, float height, Color color,
|
||||||
Stroke stroke, DrawListener drawListener) throws IOException {
|
Stroke stroke, DrawListener drawListener) throws IOException {
|
||||||
|
|
||||||
add(pdDocument, contentStream, upperLeft, width, height);
|
add(pdDocument, contentStream, upperLeft, width, height);
|
||||||
|
|
||||||
if (stroke != null) {
|
if (stroke != null) {
|
||||||
stroke.applyTo(contentStream);
|
stroke.applyTo(contentStream);
|
||||||
}
|
}
|
||||||
|
@ -31,29 +26,22 @@ public abstract class AbstractShape implements Shape {
|
||||||
contentStream.setStrokingColor(color);
|
contentStream.setStrokingColor(color);
|
||||||
}
|
}
|
||||||
contentStream.stroke();
|
contentStream.stroke();
|
||||||
|
|
||||||
if (drawListener != null) {
|
if (drawListener != null) {
|
||||||
drawListener.drawn(this, upperLeft, width, height);
|
drawListener.drawn(this, upperLeft, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fill(PDDocument pdDocument, PDPageContentStream contentStream,
|
public void fill(PDDocument pdDocument, PDPageContentStream contentStream,
|
||||||
Position upperLeft, float width, float height, Color color,
|
Position upperLeft, float width, float height, Color color,
|
||||||
DrawListener drawListener) throws IOException {
|
DrawListener drawListener) throws IOException {
|
||||||
|
|
||||||
add(pdDocument, contentStream, upperLeft, width, height);
|
add(pdDocument, contentStream, upperLeft, width, height);
|
||||||
|
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
contentStream.setNonStrokingColor(color);
|
contentStream.setNonStrokingColor(color);
|
||||||
}
|
}
|
||||||
CompatibilityHelper.fillNonZero(contentStream);
|
contentStream.fill();
|
||||||
|
|
||||||
if (drawListener != null) {
|
if (drawListener != null) {
|
||||||
drawListener.drawn(this, upperLeft, width, height);
|
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.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,6 +13,7 @@ public class RoundRect extends AbstractShape {
|
||||||
private final static float BEZ = 0.551915024494f;
|
private final static float BEZ = 0.551915024494f;
|
||||||
|
|
||||||
private final float cornerRadiusX;
|
private final float cornerRadiusX;
|
||||||
|
|
||||||
private final float cornerRadiusY;
|
private final float cornerRadiusY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,18 +96,16 @@ public class RoundRect extends AbstractShape {
|
||||||
|
|
||||||
contentStream.moveTo(a.getX(), a.getY());
|
contentStream.moveTo(a.getX(), a.getY());
|
||||||
addLine(contentStream, a.getX(), a.getY(), b.getX(), b.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());
|
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());
|
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());
|
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());
|
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());
|
g.getY() - bezY, g.getX(), g.getY());
|
||||||
addLine(contentStream, g.getX(), g.getY(), h.getX(), h.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());
|
a.getY(), a.getX(), a.getY());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +118,7 @@ public class RoundRect extends AbstractShape {
|
||||||
float y1, float x2, float y2) throws IOException {
|
float y1, float x2, float y2) throws IOException {
|
||||||
float xMid = (x1 + x2) / 2f;
|
float xMid = (x1 + x2) / 2f;
|
||||||
float yMid = (y1 + y2) / 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 org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import java.io.IOException;
|
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.
|
* This is a container for all information needed to perform a stroke.
|
||||||
|
@ -51,6 +53,7 @@ public class Stroke {
|
||||||
public static class DashPattern {
|
public static class DashPattern {
|
||||||
|
|
||||||
private final float[] pattern;
|
private final float[] pattern;
|
||||||
|
|
||||||
private final float phase;
|
private final float phase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,8 +109,11 @@ public class Stroke {
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CapStyle capStyle;
|
private final CapStyle capStyle;
|
||||||
|
|
||||||
private final JoinStyle joinStyle;
|
private final JoinStyle joinStyle;
|
||||||
|
|
||||||
private final DashPattern dashPattern;
|
private final DashPattern dashPattern;
|
||||||
|
|
||||||
private final float lineWidth;
|
private final float lineWidth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,18 +174,20 @@ public class Stroke {
|
||||||
* @param contentStream the content stream to apply this stroke to.
|
* @param contentStream the content stream to apply this stroke to.
|
||||||
* @throws IOException by PDFBox.
|
* @throws IOException by PDFBox.
|
||||||
*/
|
*/
|
||||||
public void applyTo(final PDPageContentStream contentStream)
|
public void applyTo(PDPageContentStream contentStream) throws IOException {
|
||||||
throws IOException {
|
|
||||||
if (getCapStyle() != null) {
|
if (getCapStyle() != null) {
|
||||||
|
Logger.getLogger("").info(" cap style = " + getCapStyle().value());
|
||||||
contentStream.setLineCapStyle(getCapStyle().value());
|
contentStream.setLineCapStyle(getCapStyle().value());
|
||||||
}
|
}
|
||||||
if (getJoinStyle() != null) {
|
if (getJoinStyle() != null) {
|
||||||
|
Logger.getLogger("").info(" join style = " + getJoinStyle().value());
|
||||||
contentStream.setLineJoinStyle(getJoinStyle().value());
|
contentStream.setLineJoinStyle(getJoinStyle().value());
|
||||||
}
|
}
|
||||||
if (getDashPattern() != null) {
|
if (getDashPattern() != null) {
|
||||||
contentStream.setLineDashPattern(getDashPattern().getPattern(),
|
Logger.getLogger("").info(" dash pattern = " + Arrays.asList(getDashPattern().getPattern()));
|
||||||
getDashPattern().getPhase());
|
contentStream.setLineDashPattern(getDashPattern().getPattern(), getDashPattern().getPhase());
|
||||||
}
|
}
|
||||||
|
Logger.getLogger("").info(" line width = " + getLineWidth());
|
||||||
contentStream.setLineWidth(getLineWidth());
|
contentStream.setLineWidth(getLineWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,10 +204,14 @@ public class Stroke {
|
||||||
* A builder providing a fluent interface for creating a stroke.
|
* A builder providing a fluent interface for creating a stroke.
|
||||||
*/
|
*/
|
||||||
public static class StrokeBuilder {
|
public static class StrokeBuilder {
|
||||||
|
|
||||||
private CapStyle capStyle = CapStyle.Cap;
|
private CapStyle capStyle = CapStyle.Cap;
|
||||||
|
|
||||||
private JoinStyle joinStyle = JoinStyle.Miter;
|
private JoinStyle joinStyle = JoinStyle.Miter;
|
||||||
|
|
||||||
private DashPattern dashPattern;
|
private DashPattern dashPattern;
|
||||||
float lineWidth = 1f;
|
|
||||||
|
private float lineWidth = 1f;
|
||||||
|
|
||||||
public StrokeBuilder capStyle(CapStyle capStyle) {
|
public StrokeBuilder capStyle(CapStyle capStyle) {
|
||||||
this.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;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines an area with a width and height.
|
* Defines an area with a width and height.
|
||||||
*/
|
*/
|
||||||
|
@ -9,13 +7,11 @@ public interface Area {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the width of the area.
|
* @return the width of the area.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
float getWidth() throws IOException;
|
float getWidth();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the height of the area.
|
* @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;
|
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 org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A control fragment has no drawable representation but is meant to control the
|
* 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 {
|
public class ControlFragment implements TextFragment {
|
||||||
|
|
||||||
protected final static FontDescriptor DEFAULT_FONT_DESCRIPTOR = new FontDescriptor(
|
protected final static FontDescriptor DEFAULT_FONT_DESCRIPTOR =
|
||||||
PDType1Font.HELVETICA, 11);
|
new FontDescriptor(BaseFont.HELVETICA, 11);
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private final String text;
|
private final String text;
|
||||||
|
|
||||||
private final FontDescriptor fontDescriptor;
|
private final FontDescriptor fontDescriptor;
|
||||||
|
|
||||||
private final Color color;
|
private final Color color;
|
||||||
|
|
||||||
protected ControlFragment(final String text,
|
protected ControlFragment(String text, FontDescriptor fontDescriptor) {
|
||||||
final FontDescriptor fontDescriptor) {
|
|
||||||
this(null, text, fontDescriptor, Color.black);
|
this(null, text, fontDescriptor, Color.black);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ControlFragment(final String name, final String text,
|
protected ControlFragment(String name, final String text,
|
||||||
final FontDescriptor fontDescriptor, final Color color) {
|
FontDescriptor fontDescriptor, final Color color) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
if (this.name == null) {
|
if (this.name == null) {
|
||||||
this.name = getClass().getSimpleName();
|
this.name = getClass().getSimpleName();
|
||||||
|
@ -36,12 +37,12 @@ public class ControlFragment implements TextFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWidth() throws IOException {
|
public float getWidth() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getHeight() throws IOException {
|
public float getHeight() {
|
||||||
return getFontDescriptor() == null ? 0 : getFontDescriptor().getSize();
|
return getFontDescriptor() == null ? 0 : getFontDescriptor().getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.text;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
|
||||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control fragment that represents a indent in text.
|
* Control fragment that represents a indent in text.
|
||||||
|
@ -24,12 +24,9 @@ public class Indent extends ControlFragment {
|
||||||
*
|
*
|
||||||
* @param indentWidth the indentation.
|
* @param indentWidth the indentation.
|
||||||
* @param indentUnit the indentation unit.
|
* @param indentUnit the indentation unit.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public Indent(final float indentWidth, final SpaceUnit indentUnit)
|
public Indent(float indentWidth, SpaceUnit indentUnit) {
|
||||||
throws IOException {
|
this("", indentWidth, indentUnit, DEFAULT_FONT_DESCRIPTOR, Alignment.LEFT, Color.black);
|
||||||
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 label the label of the indentation.
|
||||||
* @param indentWidth the indentation.
|
* @param indentWidth the indentation.
|
||||||
* @param indentUnit the indentation unit.
|
* @param indentUnit the indentation unit.
|
||||||
* @param fontSize the font size, resp. the height of the new line.
|
* @param descriptor the font size, resp. the height of the new line, the font to use.
|
||||||
* @param font the font to use.
|
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public Indent(final String label, final float indentWidth,
|
public Indent(String label, float indentWidth, SpaceUnit indentUnit, FontDescriptor descriptor) {
|
||||||
final SpaceUnit indentUnit, final float fontSize, final PDFont font)
|
this(label, indentWidth, indentUnit, descriptor, Alignment.LEFT, Color.black);
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
this(label, indentWidth, indentUnit, fontSize, font, Alignment.LEFT,
|
|
||||||
Color.black);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,38 +51,15 @@ public class Indent extends ControlFragment {
|
||||||
* @param label the label of the indentation.
|
* @param label the label of the indentation.
|
||||||
* @param indentWidth the indentation.
|
* @param indentWidth the indentation.
|
||||||
* @param indentUnit the indentation unit.
|
* @param indentUnit the indentation unit.
|
||||||
* @param fontSize the font size, resp. the height of the new line.
|
* @param fontDescriptor the font size, resp. the height of the new line, the font to use.
|
||||||
* @param font the font to use.
|
|
||||||
* @param alignment the alignment of the label.
|
* @param alignment the alignment of the label.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public Indent(final String label, final float indentWidth,
|
public Indent(String label,
|
||||||
final SpaceUnit indentUnit, final float fontSize,
|
float indentWidth,
|
||||||
final PDFont font, final Alignment alignment) throws IOException {
|
SpaceUnit indentUnit,
|
||||||
this(label, indentWidth, indentUnit, fontSize, font, alignment,
|
FontDescriptor fontDescriptor,
|
||||||
Color.black);
|
Alignment alignment) {
|
||||||
}
|
this(label, indentWidth, indentUnit, fontDescriptor, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,37 +71,40 @@ public class Indent extends ControlFragment {
|
||||||
* @param fontDescriptor the font and size associated with this new line.
|
* @param fontDescriptor the font and size associated with this new line.
|
||||||
* @param alignment the alignment of the label.
|
* @param alignment the alignment of the label.
|
||||||
* @param color the color to use.
|
* @param color the color to use.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public Indent(final String label, final float indentWidth,
|
public Indent(String label,
|
||||||
final SpaceUnit indentUnit, final FontDescriptor fontDescriptor,
|
float indentWidth,
|
||||||
final Alignment alignment, final Color color) throws IOException {
|
SpaceUnit indentUnit,
|
||||||
|
FontDescriptor fontDescriptor,
|
||||||
|
Alignment alignment,
|
||||||
|
Color color) {
|
||||||
super("INDENT", label, fontDescriptor, color);
|
super("INDENT", label, fontDescriptor, color);
|
||||||
|
try {
|
||||||
float indent = calculateIndent(indentWidth, indentUnit, fontDescriptor);
|
float indent = calculateIndent(indentWidth, indentUnit, fontDescriptor);
|
||||||
float textWidth = 0;
|
float textWidth = 0;
|
||||||
if (label != null && !label.isEmpty()) {
|
if (label != null && !label.isEmpty()) {
|
||||||
textWidth = fontDescriptor.getSize()
|
textWidth = fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getStringWidth(label) / 1000f;
|
||||||
* fontDescriptor.getFont().getStringWidth(label) / 1000f;
|
|
||||||
}
|
|
||||||
float marginLeft = 0;
|
|
||||||
float marginRight = 0;
|
|
||||||
if (textWidth < indent) {
|
|
||||||
switch (alignment) {
|
|
||||||
case LEFT:
|
|
||||||
marginRight = indent - textWidth;
|
|
||||||
break;
|
|
||||||
case RIGHT:
|
|
||||||
marginLeft = indent - textWidth;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
marginLeft = (indent - textWidth) / 2f;
|
|
||||||
marginRight = marginLeft;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
float marginLeft = 0;
|
||||||
|
float marginRight = 0;
|
||||||
|
if (textWidth < indent) {
|
||||||
|
switch (alignment) {
|
||||||
|
case LEFT:
|
||||||
|
marginRight = indent - textWidth;
|
||||||
|
break;
|
||||||
|
case RIGHT:
|
||||||
|
marginLeft = indent - textWidth;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
marginLeft = (indent - textWidth) / 2f;
|
||||||
|
marginRight = marginLeft;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
styledText = new StyledText(label, fontDescriptor, getColor(), 0, marginLeft, marginRight);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
styledText = new StyledText(label, getFontDescriptor(), getColor(), 0,
|
|
||||||
marginLeft, marginRight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,8 +118,7 @@ public class Indent extends ControlFragment {
|
||||||
indentPt, 0);
|
indentPt, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private float calculateIndent(final float indentWidth,
|
private float calculateIndent(float indentWidth, SpaceUnit indentUnit, final FontDescriptor fontDescriptor)
|
||||||
final SpaceUnit indentUnit, final FontDescriptor fontDescriptor)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (indentWidth < 0) {
|
if (indentWidth < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -157,13 +127,10 @@ public class Indent extends ControlFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWidth() throws IOException {
|
public float getWidth() {
|
||||||
return styledText.getWidth();
|
return styledText.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a styled text representation of the indent.
|
|
||||||
*/
|
|
||||||
public StyledText toStyledText() {
|
public StyledText toStyledText() {
|
||||||
return styledText;
|
return styledText;
|
||||||
}
|
}
|
||||||
|
@ -172,5 +139,4 @@ public class Indent extends ControlFragment {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ControlFragment [" + getName() + ", " + styledText + "]";
|
return "ControlFragment [" + getName() + ", " + styledText + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.text;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import org.xbib.graphics.pdfbox.layout.util.Enumerator;
|
import org.xbib.graphics.pdfbox.layout.util.Enumerator;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.EnumeratorFactory;
|
import org.xbib.graphics.pdfbox.layout.util.EnumeratorFactory;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -23,8 +21,7 @@ public class IndentCharacters {
|
||||||
/**
|
/**
|
||||||
* Represent un-indentation, means effectively indent of 0.
|
* Represent un-indentation, means effectively indent of 0.
|
||||||
*/
|
*/
|
||||||
public static IndentCharacter UNINDENT_CHARACTER = new IndentCharacter("0",
|
public static IndentCharacter UNINDENT_CHARACTER = new IndentCharacter("0", "0", "pt");
|
||||||
"0", "pt");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An <code>--{7em}</code> indicates an indentation of 7 characters in markup,
|
* An <code>--{7em}</code> indicates an indentation of 7 characters in markup,
|
||||||
|
@ -38,8 +35,9 @@ public class IndentCharacters {
|
||||||
protected float indentWidth = 4;
|
protected float indentWidth = 4;
|
||||||
protected SpaceUnit indentUnit = SpaceUnit.em;
|
protected SpaceUnit indentUnit = SpaceUnit.em;
|
||||||
|
|
||||||
public IndentCharacter(final String level, final String indentWidth,
|
public IndentCharacter(String level,
|
||||||
final String indentUnit) {
|
String indentWidth,
|
||||||
|
String indentUnit) {
|
||||||
super("INDENT", IndentCharacterFactory.TO_ESCAPE);
|
super("INDENT", IndentCharacterFactory.TO_ESCAPE);
|
||||||
try {
|
try {
|
||||||
this.level = level == null ? 0 : level.length() + 1;
|
this.level = level == null ? 0 : level.length() + 1;
|
||||||
|
@ -78,16 +76,12 @@ public class IndentCharacters {
|
||||||
* Creates the actual {@link Indent} fragment from this control
|
* Creates the actual {@link Indent} fragment from this control
|
||||||
* character.
|
* character.
|
||||||
*
|
*
|
||||||
* @param fontSize the current font size.
|
* @param descriptor the current font size, the current font.
|
||||||
* @param font the current font.
|
|
||||||
* @param color the color to use.
|
* @param color the color to use.
|
||||||
* @return the new Indent.
|
* @return the new Indent.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public Indent createNewIndent(final float fontSize, final PDFont font,
|
public Indent createNewIndent(FontDescriptor descriptor, Color color) {
|
||||||
final Color color) throws IOException {
|
return new Indent(nextLabel(), level * indentWidth, indentUnit, descriptor, Alignment.RIGHT, color);
|
||||||
return new Indent(nextLabel(), level * indentWidth, indentUnit,
|
|
||||||
fontSize, font, Alignment.RIGHT, color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,7 +124,7 @@ public class IndentCharacters {
|
||||||
* markup, using <code>--</code> as the bullet. The number, the unit, bullet
|
* markup, using <code>--</code> as the bullet. The number, the unit, bullet
|
||||||
* character and the brackets are optional. Default indentation is 4
|
* character and the brackets are optional. Default indentation is 4
|
||||||
* characters, default unit is <code>em</code> and the default bullet
|
* 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 ('\').
|
* escaped with a backslash ('\').
|
||||||
*/
|
*/
|
||||||
public static class ListCharacter extends IndentCharacter {
|
public static class ListCharacter extends IndentCharacter {
|
||||||
|
@ -146,7 +140,7 @@ public class IndentCharacters {
|
||||||
label += " ";
|
label += " ";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
label = CompatibilityHelper.getBulletCharacter(getLevel()) + " ";
|
label = getBulletCharacter(getLevel()) + " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +310,17 @@ public class IndentCharacters {
|
||||||
public boolean patternMatchesBeginOfLine() {
|
public boolean patternMatchesBeginOfLine() {
|
||||||
return true;
|
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 {
|
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.
|
* Creates a new line with the given font descriptor.
|
||||||
*
|
*
|
||||||
* @param fontDescriptor the font and size associated with this new line.
|
* @param fontDescriptor the font and size associated with this new line.
|
||||||
*/
|
*/
|
||||||
public NewLine(final FontDescriptor fontDescriptor) {
|
public NewLine(FontDescriptor fontDescriptor) {
|
||||||
super("\n", fontDescriptor);
|
super("\n", fontDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,9 @@ public enum SpaceUnit {
|
||||||
* @return the size in pt.
|
* @return the size in pt.
|
||||||
* @throws IOException by pdfbox
|
* @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) {
|
if (this == em) {
|
||||||
return fontDescriptor.getSize()
|
return fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getAverageFontWidth() / 1000 * size;
|
||||||
* fontDescriptor.getFont().getAverageFontWidth() / 1000 * size;
|
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.text;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
|
||||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class representing drawable text styled with font, size, color etc.
|
* Base class representing drawable text styled with font, size, color etc.
|
||||||
|
@ -27,51 +27,13 @@ public class StyledText implements TextFragment {
|
||||||
*/
|
*/
|
||||||
private Float width = null;
|
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.
|
* Creates a styled text.
|
||||||
*
|
*
|
||||||
* @param text the text to draw. Must not contain line feeds ('\n').
|
* @param text the text to draw. Must not contain line feeds ('\n').
|
||||||
* @param fontDescriptor the font to use.
|
* @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);
|
this(text, fontDescriptor, Color.black);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +44,7 @@ public class StyledText implements TextFragment {
|
||||||
* @param fontDescriptor the font to use.
|
* @param fontDescriptor the font to use.
|
||||||
* @param color the color to use.
|
* @param color the color to use.
|
||||||
*/
|
*/
|
||||||
public StyledText(final String text, final FontDescriptor fontDescriptor,
|
public StyledText(String text, FontDescriptor fontDescriptor, Color color) {
|
||||||
final Color color) {
|
|
||||||
this(text, fontDescriptor, color, 0, 0, 0);
|
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 leftMargin the margin left to the text.
|
||||||
* @param rightMargin the margin right to the text.
|
* @param rightMargin the margin right to the text.
|
||||||
*/
|
*/
|
||||||
public StyledText(final String text, final FontDescriptor fontDescriptor,
|
public StyledText(String text,
|
||||||
final Color color, final float baselineOffset,
|
FontDescriptor fontDescriptor,
|
||||||
final float leftMargin, final float rightMargin) {
|
Color color,
|
||||||
|
float baselineOffset, float leftMargin, float rightMargin) {
|
||||||
if (text.contains("\n")) {
|
if (text.contains("\n")) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("StyledText must not contain line breaks, use TextFragment.LINEBREAK for that");
|
||||||
"StyledText must not contain line breaks, use TextFragment.LINEBREAK for that");
|
|
||||||
}
|
}
|
||||||
if (leftMargin < 0) {
|
if (leftMargin < 0) {
|
||||||
throw new IllegalArgumentException("leftMargin must be >= 0");
|
throw new IllegalArgumentException("leftMargin must be >= 0");
|
||||||
|
@ -133,11 +94,9 @@ public class StyledText implements TextFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWidth() throws IOException {
|
public float getWidth() {
|
||||||
if (width == null) {
|
if (width == null) {
|
||||||
width = getFontDescriptor().getSize()
|
width = getWidth(getFontDescriptor(), getText());
|
||||||
* getFontDescriptor().getFont().getStringWidth(getText())
|
|
||||||
/ 1000;
|
|
||||||
width += leftMargin;
|
width += leftMargin;
|
||||||
width += rightMargin;
|
width += rightMargin;
|
||||||
}
|
}
|
||||||
|
@ -149,7 +108,7 @@ public class StyledText implements TextFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getHeight() throws IOException {
|
public float getHeight() {
|
||||||
return getFontDescriptor().getSize();
|
return getFontDescriptor().getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,9 +117,7 @@ public class StyledText implements TextFragment {
|
||||||
* @throws IOException by pdfbox.
|
* @throws IOException by pdfbox.
|
||||||
*/
|
*/
|
||||||
public float getAsent() throws IOException {
|
public float getAsent() throws IOException {
|
||||||
return getFontDescriptor().getSize()
|
return getFontDescriptor().getSize() * getFontDescriptor().getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||||
* getFontDescriptor().getFont().getFontDescriptor().getAscent()
|
|
||||||
/ 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getBaselineOffset() {
|
public float getBaselineOffset() {
|
||||||
|
@ -206,12 +163,19 @@ public class StyledText implements TextFragment {
|
||||||
return inheritAttributes(text, getLeftMargin(), getRightMargin());
|
return inheritAttributes(text, getLeftMargin(), getRightMargin());
|
||||||
}
|
}
|
||||||
|
|
||||||
public StyledText inheritAttributes(String text, float leftMargin,
|
public StyledText inheritAttributes(String text, float leftMargin, float rightMargin) {
|
||||||
float rightMargin) {
|
|
||||||
return new StyledText(text, getFontDescriptor(), getColor(),
|
return new StyledText(text, getFontDescriptor(), getColor(),
|
||||||
getBaselineOffset(), leftMargin, rightMargin);
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "StyledText [text=" + text + ", fontDescriptor="
|
return "StyledText [text=" + text + ", fontDescriptor="
|
||||||
|
@ -219,5 +183,4 @@ public class StyledText implements TextFragment {
|
||||||
+ ", leftMargin=" + leftMargin + ", rightMargin=" + rightMargin
|
+ ", leftMargin=" + leftMargin + ", rightMargin=" + rightMargin
|
||||||
+ ", baselineOffset=" + baselineOffset + "]";
|
+ ", baselineOffset=" + baselineOffset + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
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.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -68,9 +68,8 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
return (T) cache.get(key);
|
return (T) cache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addText(final String text, final float fontSize,
|
public void addText(String text, float fontSize, Font font) {
|
||||||
final Font font) throws IOException {
|
add(TextFlowUtil.createTextFlow(text, new FontDescriptor(font, fontSize)));
|
||||||
add(TextFlowUtil.createTextFlow(text, fontSize, font));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,13 +77,20 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
*
|
*
|
||||||
* @param markup the markup to add.
|
* @param markup the markup to add.
|
||||||
* @param fontSize the font size to use.
|
* @param fontSize the font size to use.
|
||||||
* @param baseFont the base font describing the bundle of
|
* @param font the font
|
||||||
* plain/blold/italic/bold-italic fonts.
|
|
||||||
* @throws IOException by PDFBox
|
|
||||||
*/
|
*/
|
||||||
public void addMarkup(final String markup, final float fontSize,
|
public void addMarkup(String markup, float fontSize, Font font) {
|
||||||
final Font baseFont) throws IOException {
|
add(TextFlowUtil.createTextFlowFromMarkup(markup, new FontDescriptor(font, fontSize)));
|
||||||
add(TextFlowUtil.createTextFlowFromMarkup(markup, fontSize, baseFont));
|
}
|
||||||
|
|
||||||
|
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.
|
* @param sequence the sequence to add.
|
||||||
*/
|
*/
|
||||||
public void add(final TextSequence sequence) {
|
public void add(TextSequence sequence) {
|
||||||
for (TextFragment fragment : sequence) {
|
for (TextFragment fragment : sequence) {
|
||||||
add(fragment);
|
add(fragment);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +109,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
*
|
*
|
||||||
* @param fragment the fragment to add.
|
* @param fragment the fragment to add.
|
||||||
*/
|
*/
|
||||||
public void add(final TextFragment fragment) {
|
public void add(TextFragment fragment) {
|
||||||
text.add(fragment);
|
text.add(fragment);
|
||||||
clearCache();
|
clearCache();
|
||||||
}
|
}
|
||||||
|
@ -199,7 +205,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWidth() throws IOException {
|
public float getWidth() {
|
||||||
Float width = getCachedValue(WIDTH, Float.class);
|
Float width = getCachedValue(WIDTH, Float.class);
|
||||||
if (width == null) {
|
if (width == null) {
|
||||||
width = TextSequenceUtil.getWidth(this, getMaxWidth());
|
width = TextSequenceUtil.getWidth(this, getMaxWidth());
|
||||||
|
@ -209,7 +215,7 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getHeight() throws IOException {
|
public float getHeight() {
|
||||||
Float height = getCachedValue(HEIGHT, Float.class);
|
Float height = getCachedValue(HEIGHT, Float.class);
|
||||||
if (height == null) {
|
if (height == null) {
|
||||||
height = TextSequenceUtil.getHeight(this, getMaxWidth(),
|
height = TextSequenceUtil.getHeight(this, getMaxWidth(),
|
||||||
|
@ -221,23 +227,22 @@ public class TextFlow implements TextSequence, WidthRespecting {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
||||||
Alignment alignment, DrawListener drawListener) throws IOException {
|
Alignment alignment, DrawListener drawListener) {
|
||||||
TextSequenceUtil.drawText(this, contentStream, upperLeft, drawListener, alignment,
|
TextSequenceUtil.drawText(this, contentStream, upperLeft, drawListener, alignment,
|
||||||
getMaxWidth(), getLineSpacing(),
|
getMaxWidth(), getLineSpacing(),
|
||||||
isApplyLineSpacingToFirstLine());
|
isApplyLineSpacingToFirstLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawTextRightAligned(PDPageContentStream contentStream,
|
public void drawTextRightAligned(PDPageContentStream contentStream,
|
||||||
Position endOfFirstLine, DrawListener drawListener) throws IOException {
|
Position endOfFirstLine, DrawListener drawListener) {
|
||||||
drawText(contentStream, endOfFirstLine.add(-getWidth(), 0),
|
drawText(contentStream, endOfFirstLine.add(-getWidth(), 0),
|
||||||
Alignment.RIGHT, drawListener);
|
Alignment.RIGHT, drawListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a copy of this text flow where all leading {@link NewLine}s are removed.
|
* @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)) {
|
if (text.size() == 0 || !(text.get(0) instanceof NewLine)) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.text;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.font.PDFont;
|
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import org.xbib.graphics.pdfbox.layout.font.Font;
|
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotatedStyledText;
|
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.Annotation;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationCharacters;
|
import org.xbib.graphics.pdfbox.layout.text.annotations.AnnotationCharacters;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -17,11 +15,9 @@ import java.util.regex.Matcher;
|
||||||
|
|
||||||
public class TextFlowUtil {
|
public class TextFlowUtil {
|
||||||
|
|
||||||
public static TextFlow createTextFlow(String text,
|
public static TextFlow createTextFlow(String text, FontDescriptor descriptor) {
|
||||||
float fontSize,
|
|
||||||
Font baseFont) throws IOException {
|
|
||||||
final Iterable<CharSequence> parts = fromPlainText(text);
|
final Iterable<CharSequence> parts = fromPlainText(text);
|
||||||
return createTextFlow(parts, fontSize, baseFont);
|
return createTextFlow(parts, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,41 +48,33 @@ public class TextFlowUtil {
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param markup the markup text.
|
* @param markup the markup text.
|
||||||
* @param fontSize the font size to use.
|
* @param descriptor the font size to use, and the font.
|
||||||
* @param baseFont the font.
|
|
||||||
* @return the created text flow.
|
* @return the created text flow.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static TextFlow createTextFlowFromMarkup(final String markup,
|
public static TextFlow createTextFlowFromMarkup(String markup, FontDescriptor descriptor) {
|
||||||
final float fontSize,
|
|
||||||
Font baseFont) throws IOException {
|
|
||||||
final Iterable<CharSequence> parts = fromMarkup(markup);
|
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.
|
* Actually creates the text flow from the given (markup) text.
|
||||||
*
|
*
|
||||||
* @param parts the parts to create the text flow from.
|
* @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.
|
* @return the created text flow.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
protected static TextFlow createTextFlow(final Iterable<CharSequence> parts,
|
protected static TextFlow createTextFlow(Iterable<CharSequence> parts, FontDescriptor descriptor) {
|
||||||
final float fontSize, Font baseFont)
|
|
||||||
throws IOException {
|
|
||||||
final TextFlow result = new TextFlow();
|
final TextFlow result = new TextFlow();
|
||||||
boolean bold = false;
|
boolean bold = false;
|
||||||
boolean italic = false;
|
boolean italic = false;
|
||||||
Color color = Color.black;
|
Color color = Color.black;
|
||||||
ControlCharacters.MetricsControlCharacter metricsControl = null;
|
ControlCharacters.MetricsControlCharacter metricsControl = null;
|
||||||
Map<Class<? extends Annotation>, Annotation> annotationMap = new HashMap<Class<? extends Annotation>, Annotation>();
|
Map<Class<? extends Annotation>, Annotation> annotationMap = new HashMap<>();
|
||||||
Stack<IndentCharacters.IndentCharacter> indentStack = new Stack<IndentCharacters.IndentCharacter>();
|
Stack<IndentCharacters.IndentCharacter> indentStack = new Stack<>();
|
||||||
for (final CharSequence fragment : parts) {
|
for (final CharSequence fragment : parts) {
|
||||||
|
|
||||||
if (fragment instanceof ControlCharacter) {
|
if (fragment instanceof ControlCharacter) {
|
||||||
if (fragment instanceof ControlCharacters.NewLineControlCharacter) {
|
if (fragment instanceof ControlCharacters.NewLineControlCharacter) {
|
||||||
result.add(new NewLine(fontSize));
|
result.add(new NewLine(descriptor));
|
||||||
}
|
}
|
||||||
if (fragment instanceof ControlCharacters.BoldControlCharacter) {
|
if (fragment instanceof ControlCharacters.BoldControlCharacter) {
|
||||||
bold = !bold;
|
bold = !bold;
|
||||||
|
@ -110,7 +98,6 @@ public class TextFlowUtil {
|
||||||
}
|
}
|
||||||
if (fragment instanceof ControlCharacters.MetricsControlCharacter) {
|
if (fragment instanceof ControlCharacters.MetricsControlCharacter) {
|
||||||
if (metricsControl != null && metricsControl.toString().equals(fragment.toString())) {
|
if (metricsControl != null && metricsControl.toString().equals(fragment.toString())) {
|
||||||
// end marker
|
|
||||||
metricsControl = null;
|
metricsControl = null;
|
||||||
} else {
|
} else {
|
||||||
metricsControl = (ControlCharacters.MetricsControlCharacter) fragment;
|
metricsControl = (ControlCharacters.MetricsControlCharacter) fragment;
|
||||||
|
@ -119,10 +106,8 @@ public class TextFlowUtil {
|
||||||
if (fragment instanceof IndentCharacters.IndentCharacter) {
|
if (fragment instanceof IndentCharacters.IndentCharacter) {
|
||||||
IndentCharacters.IndentCharacter currentIndent = (IndentCharacters.IndentCharacter) fragment;
|
IndentCharacters.IndentCharacter currentIndent = (IndentCharacters.IndentCharacter) fragment;
|
||||||
if (currentIndent.getLevel() == 0) {
|
if (currentIndent.getLevel() == 0) {
|
||||||
// indentation of 0 resets indent
|
|
||||||
indentStack.clear();
|
indentStack.clear();
|
||||||
result.add(Indent.UNINDENT);
|
result.add(Indent.UNINDENT);
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
IndentCharacters.IndentCharacter last = null;
|
IndentCharacters.IndentCharacter last = null;
|
||||||
while (!indentStack.isEmpty()
|
while (!indentStack.isEmpty()
|
||||||
|
@ -135,26 +120,27 @@ public class TextFlowUtil {
|
||||||
currentIndent = last;
|
currentIndent = last;
|
||||||
}
|
}
|
||||||
indentStack.push(currentIndent);
|
indentStack.push(currentIndent);
|
||||||
result.add(currentIndent.createNewIndent(fontSize,
|
FontDescriptor fontDescriptor =
|
||||||
baseFont.getPlainFont(), color));
|
new FontDescriptor(descriptor.getFont(), descriptor.getSize(), bold, italic);
|
||||||
|
result.add(currentIndent.createNewIndent(fontDescriptor, color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
PDFont font = getFont(bold, italic, baseFont);
|
|
||||||
float baselineOffset = 0;
|
float baselineOffset = 0;
|
||||||
float currentFontSize = fontSize;
|
float currentFontSize = descriptor.getSize();
|
||||||
if (metricsControl != null) {
|
if (metricsControl != null) {
|
||||||
baselineOffset = metricsControl.getBaselineOffsetScale() * fontSize;
|
baselineOffset = metricsControl.getBaselineOffsetScale() * descriptor.getSize();
|
||||||
currentFontSize *= metricsControl.getFontScale();
|
currentFontSize *= metricsControl.getFontScale();
|
||||||
}
|
}
|
||||||
|
FontDescriptor fontDescriptor = new FontDescriptor(descriptor.getFont(), currentFontSize, bold, italic);
|
||||||
if (annotationMap.isEmpty()) {
|
if (annotationMap.isEmpty()) {
|
||||||
StyledText styledText = new StyledText(fragment.toString(),
|
StyledText styledText = new StyledText(fragment.toString(), fontDescriptor,
|
||||||
currentFontSize, font, color, baselineOffset);
|
color, baselineOffset, 0, 0);
|
||||||
result.add(styledText);
|
result.add(styledText);
|
||||||
} else {
|
} else {
|
||||||
AnnotatedStyledText styledText = new AnnotatedStyledText(
|
AnnotatedStyledText styledText =
|
||||||
fragment.toString(), currentFontSize, baseFont, color, baselineOffset,
|
new AnnotatedStyledText(fragment.toString(), fontDescriptor,
|
||||||
annotationMap.values());
|
color, baselineOffset, 0, 0, annotationMap.values());
|
||||||
result.add(styledText);
|
result.add(styledText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,33 +148,6 @@ public class TextFlowUtil {
|
||||||
return result;
|
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
|
* Creates a char sequence where new-line is replaced by the corresponding
|
||||||
* {@link ControlCharacter}.
|
* {@link ControlCharacter}.
|
||||||
|
@ -233,25 +192,19 @@ public class TextFlowUtil {
|
||||||
* @param markup the markup.
|
* @param markup the markup.
|
||||||
* @return the create char sequence.
|
* @return the create char sequence.
|
||||||
*/
|
*/
|
||||||
public static Iterable<CharSequence> fromMarkup(
|
public static Iterable<CharSequence> fromMarkup(Iterable<CharSequence> markup) {
|
||||||
final Iterable<CharSequence> markup) {
|
|
||||||
Iterable<CharSequence> text = markup;
|
Iterable<CharSequence> text = markup;
|
||||||
text = splitByControlCharacter(ControlCharacters.NEWLINE_FACTORY, text);
|
text = splitByControlCharacter(ControlCharacters.NEWLINE_FACTORY, text);
|
||||||
text = splitByControlCharacter(ControlCharacters.METRICS_FACTORY, text);
|
text = splitByControlCharacter(ControlCharacters.METRICS_FACTORY, text);
|
||||||
text = splitByControlCharacter(ControlCharacters.BOLD_FACTORY, text);
|
text = splitByControlCharacter(ControlCharacters.BOLD_FACTORY, text);
|
||||||
text = splitByControlCharacter(ControlCharacters.ITALIC_FACTORY, text);
|
text = splitByControlCharacter(ControlCharacters.ITALIC_FACTORY, text);
|
||||||
text = splitByControlCharacter(ControlCharacters.COLOR_FACTORY, text);
|
text = splitByControlCharacter(ControlCharacters.COLOR_FACTORY, text);
|
||||||
|
|
||||||
for (AnnotationCharacters.AnnotationControlCharacterFactory<?> annotationControlCharacterFactory : AnnotationCharacters
|
for (AnnotationCharacters.AnnotationControlCharacterFactory<?> annotationControlCharacterFactory : AnnotationCharacters
|
||||||
.getFactories()) {
|
.getFactories()) {
|
||||||
text = splitByControlCharacter(annotationControlCharacterFactory,
|
text = splitByControlCharacter(annotationControlCharacterFactory, text);
|
||||||
text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
text = splitByControlCharacter(IndentCharacters.INDENT_FACTORY, text);
|
text = splitByControlCharacter(IndentCharacters.INDENT_FACTORY, text);
|
||||||
|
|
||||||
text = unescapeBackslash(text);
|
text = unescapeBackslash(text);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,13 +219,12 @@ public class TextFlowUtil {
|
||||||
protected static Iterable<CharSequence> splitByControlCharacter(
|
protected static Iterable<CharSequence> splitByControlCharacter(
|
||||||
ControlCharacters.ControlCharacterFactory controlCharacterFactory,
|
ControlCharacters.ControlCharacterFactory controlCharacterFactory,
|
||||||
final Iterable<CharSequence> markup) {
|
final Iterable<CharSequence> markup) {
|
||||||
List<CharSequence> result = new ArrayList<CharSequence>();
|
List<CharSequence> result = new ArrayList<>();
|
||||||
boolean beginOfLine = true;
|
boolean beginOfLine = true;
|
||||||
for (CharSequence current : markup) {
|
for (CharSequence current : markup) {
|
||||||
if (current instanceof String) {
|
if (current instanceof String) {
|
||||||
String string = (String) current;
|
String string = (String) current;
|
||||||
int begin = 0;
|
int begin = 0;
|
||||||
|
|
||||||
if (!controlCharacterFactory.patternMatchesBeginOfLine()
|
if (!controlCharacterFactory.patternMatchesBeginOfLine()
|
||||||
|| beginOfLine) {
|
|| beginOfLine) {
|
||||||
Matcher matcher = controlCharacterFactory.getPattern()
|
Matcher matcher = controlCharacterFactory.getPattern()
|
||||||
|
@ -280,24 +232,18 @@ public class TextFlowUtil {
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String part = string.substring(begin, matcher.start());
|
String part = string.substring(begin, matcher.start());
|
||||||
begin = matcher.end();
|
begin = matcher.end();
|
||||||
|
|
||||||
if (!part.isEmpty()) {
|
if (!part.isEmpty()) {
|
||||||
String unescaped = controlCharacterFactory
|
String unescaped = controlCharacterFactory.unescape(part);
|
||||||
.unescape(part);
|
|
||||||
result.add(unescaped);
|
result.add(unescaped);
|
||||||
}
|
}
|
||||||
|
result.add(controlCharacterFactory.createControlCharacter(string, matcher, result));
|
||||||
result.add(controlCharacterFactory
|
|
||||||
.createControlCharacter(string, matcher, result));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (begin < string.length()) {
|
if (begin < string.length()) {
|
||||||
String part = string.substring(begin);
|
String part = string.substring(begin);
|
||||||
String unescaped = controlCharacterFactory.unescape(part);
|
String unescaped = controlCharacterFactory.unescape(part);
|
||||||
result.add(unescaped);
|
result.add(unescaped);
|
||||||
}
|
}
|
||||||
|
|
||||||
beginOfLine = false;
|
beginOfLine = false;
|
||||||
} else {
|
} else {
|
||||||
if (current instanceof ControlCharacters.NewLineControlCharacter) {
|
if (current instanceof ControlCharacters.NewLineControlCharacter) {
|
||||||
|
@ -305,23 +251,19 @@ public class TextFlowUtil {
|
||||||
}
|
}
|
||||||
result.add(current);
|
result.add(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<CharSequence> unescapeBackslash(
|
private static Iterable<CharSequence> unescapeBackslash(Iterable<CharSequence> chars) {
|
||||||
final Iterable<CharSequence> chars) {
|
List<CharSequence> result = new ArrayList<>();
|
||||||
List<CharSequence> result = new ArrayList<CharSequence>();
|
|
||||||
for (CharSequence current : chars) {
|
for (CharSequence current : chars) {
|
||||||
if (current instanceof String) {
|
if (current instanceof String) {
|
||||||
result.add(ControlCharacters
|
result.add(ControlCharacters.unescapeBackslash((String) current));
|
||||||
.unescapeBackslash((String) current));
|
|
||||||
} else {
|
} else {
|
||||||
result.add(current);
|
result.add(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.apache.pdfbox.util.Matrix;
|
||||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -109,7 +110,7 @@ public class TextLine implements TextSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getWidth() throws IOException {
|
public float getWidth() {
|
||||||
Float width = getCachedValue(WIDTH, Float.class);
|
Float width = getCachedValue(WIDTH, Float.class);
|
||||||
if (width == null) {
|
if (width == null) {
|
||||||
width = 0f;
|
width = 0f;
|
||||||
|
@ -122,7 +123,7 @@ public class TextLine implements TextSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getHeight() throws IOException {
|
public float getHeight() {
|
||||||
Float height = getCachedValue(HEIGHT, Float.class);
|
Float height = getCachedValue(HEIGHT, Float.class);
|
||||||
if (height == null) {
|
if (height == null) {
|
||||||
height = 0f;
|
height = 0f;
|
||||||
|
@ -136,16 +137,14 @@ public class TextLine implements TextSequence {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the (max) ascent of this line.
|
* @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);
|
Float ascent = getCachedValue(ASCENT, Float.class);
|
||||||
if (ascent == null) {
|
if (ascent == null) {
|
||||||
ascent = 0f;
|
ascent = 0f;
|
||||||
for (TextFragment fragment : this) {
|
for (TextFragment fragment : this) {
|
||||||
float currentAscent = fragment.getFontDescriptor().getSize()
|
FontDescriptor fontDescriptor = fragment.getFontDescriptor();
|
||||||
* fragment.getFontDescriptor().getFont()
|
float currentAscent = fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||||
.getFontDescriptor().getAscent() / 1000;
|
|
||||||
ascent = Math.max(ascent, currentAscent);
|
ascent = Math.max(ascent, currentAscent);
|
||||||
}
|
}
|
||||||
setCachedValue(ASCENT, ascent);
|
setCachedValue(ASCENT, ascent);
|
||||||
|
@ -155,66 +154,71 @@ public class TextLine implements TextSequence {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
public void drawText(PDPageContentStream contentStream, Position upperLeft,
|
||||||
Alignment alignment, DrawListener drawListener) throws IOException {
|
Alignment alignment, DrawListener drawListener) {
|
||||||
drawAligned(contentStream, upperLeft, alignment, getWidth(), drawListener);
|
drawAligned(contentStream, upperLeft, alignment, getWidth(), drawListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawAligned(PDPageContentStream contentStream, Position upperLeft,
|
public void drawAligned(PDPageContentStream contentStream,
|
||||||
Alignment alignment, float availableLineWidth,
|
Position upperLeft,
|
||||||
DrawListener drawListener) throws IOException {
|
Alignment alignment,
|
||||||
contentStream.saveGraphicsState();
|
float availableLineWidth,
|
||||||
float x = upperLeft.getX();
|
DrawListener drawListener) {
|
||||||
float y = upperLeft.getY() - getAscent();
|
try {
|
||||||
FontDescriptor lastFontDesc = null;
|
contentStream.saveGraphicsState();
|
||||||
float lastBaselineOffset = 0;
|
float x = upperLeft.getX();
|
||||||
Color lastColor = null;
|
float y = upperLeft.getY() - getAscent();
|
||||||
float gap = 0;
|
FontDescriptor lastFontDesc = null;
|
||||||
float extraWordSpacing = 0;
|
float lastBaselineOffset = 0;
|
||||||
if (alignment == Alignment.JUSTIFY && (getNewLine() instanceof WrappingNewLine)) {
|
Color lastColor = null;
|
||||||
extraWordSpacing = (availableLineWidth - getWidth()) / (styledTextList.size() - 1);
|
float gap = 0;
|
||||||
|
float extraWordSpacing = 0;
|
||||||
|
if (alignment == Alignment.JUSTIFY && (getNewLine() instanceof WrappingNewLine)) {
|
||||||
|
extraWordSpacing = (availableLineWidth - getWidth()) / (styledTextList.size() - 1);
|
||||||
|
}
|
||||||
|
float offset = TextSequenceUtil.getOffset(this, availableLineWidth, alignment);
|
||||||
|
x += offset;
|
||||||
|
for (StyledText styledText : styledTextList) {
|
||||||
|
Matrix matrix = Matrix.getTranslateInstance(x, y);
|
||||||
|
if (styledText.getLeftMargin() > 0) {
|
||||||
|
gap += styledText.getLeftMargin();
|
||||||
|
}
|
||||||
|
boolean moveBaseline = styledText.getBaselineOffset() != lastBaselineOffset;
|
||||||
|
if (moveBaseline || gap > 0) {
|
||||||
|
float baselineDelta = lastBaselineOffset - styledText.getBaselineOffset();
|
||||||
|
lastBaselineOffset = styledText.getBaselineOffset();
|
||||||
|
matrix = matrix.multiply(new Matrix(1, 0, 0, 1, gap, baselineDelta));
|
||||||
|
x += gap;
|
||||||
|
}
|
||||||
|
contentStream.beginText();
|
||||||
|
contentStream.setTextMatrix(matrix);
|
||||||
|
if (!styledText.getFontDescriptor().equals(lastFontDesc)) {
|
||||||
|
lastFontDesc = styledText.getFontDescriptor();
|
||||||
|
contentStream.setFont(lastFontDesc.getSelectedFont(), lastFontDesc.getSize());
|
||||||
|
}
|
||||||
|
if (!styledText.getColor().equals(lastColor)) {
|
||||||
|
lastColor = styledText.getColor();
|
||||||
|
contentStream.setNonStrokingColor(lastColor);
|
||||||
|
}
|
||||||
|
if (styledText.getText().length() > 0) {
|
||||||
|
contentStream.showText(styledText.getText());
|
||||||
|
}
|
||||||
|
contentStream.endText();
|
||||||
|
if (drawListener != null) {
|
||||||
|
drawListener.drawn(styledText,
|
||||||
|
new Position(x, y + styledText.getAsent()),
|
||||||
|
styledText.getWidthWithoutMargin(),
|
||||||
|
styledText.getHeight());
|
||||||
|
}
|
||||||
|
x += styledText.getWidthWithoutMargin();
|
||||||
|
gap = extraWordSpacing;
|
||||||
|
if (styledText.getRightMargin() > 0) {
|
||||||
|
gap += styledText.getRightMargin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentStream.restoreGraphicsState();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
float offset = TextSequenceUtil.getOffset(this, availableLineWidth, alignment);
|
|
||||||
x += offset;
|
|
||||||
for (StyledText styledText : styledTextList) {
|
|
||||||
Matrix matrix = Matrix.getTranslateInstance(x, y);
|
|
||||||
if (styledText.getLeftMargin() > 0) {
|
|
||||||
gap += styledText.getLeftMargin();
|
|
||||||
}
|
|
||||||
boolean moveBaseline = styledText.getBaselineOffset() != lastBaselineOffset;
|
|
||||||
if (moveBaseline || gap > 0) {
|
|
||||||
float baselineDelta = lastBaselineOffset - styledText.getBaselineOffset();
|
|
||||||
lastBaselineOffset = styledText.getBaselineOffset();
|
|
||||||
matrix = matrix.multiply(new Matrix(1, 0, 0, 1, gap, baselineDelta));
|
|
||||||
x += gap;
|
|
||||||
}
|
|
||||||
contentStream.beginText();
|
|
||||||
contentStream.setTextMatrix(matrix);
|
|
||||||
if (!styledText.getFontDescriptor().equals(lastFontDesc)) {
|
|
||||||
lastFontDesc = styledText.getFontDescriptor();
|
|
||||||
contentStream.setFont(lastFontDesc.getFont(), lastFontDesc.getSize());
|
|
||||||
}
|
|
||||||
if (!styledText.getColor().equals(lastColor)) {
|
|
||||||
lastColor = styledText.getColor();
|
|
||||||
contentStream.setNonStrokingColor(lastColor);
|
|
||||||
}
|
|
||||||
if (styledText.getText().length() > 0) {
|
|
||||||
contentStream.showText(styledText.getText());
|
|
||||||
}
|
|
||||||
contentStream.endText();
|
|
||||||
if (drawListener != null) {
|
|
||||||
float currentUpperLeft = y + styledText.getAsent();
|
|
||||||
drawListener.drawn(styledText,
|
|
||||||
new Position(x, currentUpperLeft),
|
|
||||||
styledText.getWidthWithoutMargin(),
|
|
||||||
styledText.getHeight());
|
|
||||||
}
|
|
||||||
x += styledText.getWidthWithoutMargin();
|
|
||||||
gap = extraWordSpacing;
|
|
||||||
if (styledText.getRightMargin() > 0) {
|
|
||||||
gap += styledText.getRightMargin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contentStream.restoreGraphicsState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.Pair;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.WordBreakerFactory;
|
import org.xbib.graphics.pdfbox.layout.util.WordBreakerFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -20,21 +21,16 @@ public class TextSequenceUtil {
|
||||||
*
|
*
|
||||||
* @param text the text to extract the lines from.
|
* @param text the text to extract the lines from.
|
||||||
* @return the list of text lines.
|
* @return the list of text lines.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static List<TextLine> getLines(final TextSequence text)
|
public static List<TextLine> getLines(final TextSequence text) {
|
||||||
throws IOException {
|
final List<TextLine> result = new ArrayList<>();
|
||||||
final List<TextLine> result = new ArrayList<TextLine>();
|
|
||||||
|
|
||||||
TextLine line = new TextLine();
|
TextLine line = new TextLine();
|
||||||
for (TextFragment fragment : text) {
|
for (TextFragment fragment : text) {
|
||||||
if (fragment instanceof NewLine) {
|
if (fragment instanceof NewLine) {
|
||||||
line.setNewLine((NewLine) fragment);
|
line.setNewLine((NewLine) fragment);
|
||||||
result.add(line);
|
result.add(line);
|
||||||
line = new TextLine();
|
line = new TextLine();
|
||||||
} else if (fragment instanceof ReplacedWhitespace) {
|
} else if (!(fragment instanceof ReplacedWhitespace)) {
|
||||||
// ignore replaced whitespace
|
|
||||||
} else {
|
|
||||||
line.add((StyledText) fragment);
|
line.add((StyledText) fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,13 +47,11 @@ public class TextSequenceUtil {
|
||||||
* @param maxWidth the max width used for word-wrapping.
|
* @param maxWidth the max width used for word-wrapping.
|
||||||
* @param maxHeight the max height for divide.
|
* @param maxHeight the max height for divide.
|
||||||
* @return the Divided element containing the parts.
|
* @return the Divided element containing the parts.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static Divided divide(final TextSequence text, final float maxWidth,
|
public static Divided divide(final TextSequence text, final float maxWidth,
|
||||||
final float maxHeight) throws IOException {
|
final float maxHeight) {
|
||||||
TextFlow wrapped = wordWrap(text, maxWidth);
|
TextFlow wrapped = wordWrap(text, maxWidth);
|
||||||
List<TextLine> lines = getLines(wrapped);
|
List<TextLine> lines = getLines(wrapped);
|
||||||
|
|
||||||
Paragraph first = new Paragraph();
|
Paragraph first = new Paragraph();
|
||||||
Paragraph tail = new Paragraph();
|
Paragraph tail = new Paragraph();
|
||||||
if (text instanceof TextFlow) {
|
if (text instanceof TextFlow) {
|
||||||
|
@ -74,24 +68,19 @@ public class TextSequenceUtil {
|
||||||
tail.setAlignment(paragraph.getAlignment());
|
tail.setAlignment(paragraph.getAlignment());
|
||||||
tail.setApplyLineSpacingToFirstLine(paragraph.isApplyLineSpacingToFirstLine());
|
tail.setApplyLineSpacingToFirstLine(paragraph.isApplyLineSpacingToFirstLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
do {
|
do {
|
||||||
TextLine line = lines.get(index);
|
TextLine line = lines.get(index);
|
||||||
first.add(line);
|
first.add(line);
|
||||||
++index;
|
++index;
|
||||||
} while (index < lines.size() && first.getHeight() < maxHeight);
|
} while (index < lines.size() && first.getHeight() < maxHeight);
|
||||||
|
|
||||||
if (first.getHeight() > maxHeight) {
|
if (first.getHeight() > maxHeight) {
|
||||||
// remove last line
|
|
||||||
--index;
|
--index;
|
||||||
TextLine line = lines.get(index);
|
TextLine line = lines.get(index);
|
||||||
for (@SuppressWarnings("unused")
|
for (TextFragment textFragment : line) {
|
||||||
TextFragment textFragment : line) {
|
|
||||||
first.removeLast();
|
first.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = index; i < lines.size(); ++i) {
|
for (int i = index; i < lines.size(); ++i) {
|
||||||
tail.add(lines.get(i));
|
tail.add(lines.get(i));
|
||||||
}
|
}
|
||||||
|
@ -104,11 +93,8 @@ public class TextSequenceUtil {
|
||||||
* @param text the text to word-wrap.
|
* @param text the text to word-wrap.
|
||||||
* @param maxWidth the max width to fit.
|
* @param maxWidth the max width to fit.
|
||||||
* @return the word-wrapped text.
|
* @return the word-wrapped text.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static TextFlow wordWrap(final TextSequence text,
|
public static TextFlow wordWrap(TextSequence text, float maxWidth) {
|
||||||
final float maxWidth) throws IOException {
|
|
||||||
|
|
||||||
float indentation = 0;
|
float indentation = 0;
|
||||||
TextFlow result = new TextFlow();
|
TextFlow result = new TextFlow();
|
||||||
float lineLength = indentation;
|
float lineLength = indentation;
|
||||||
|
@ -149,7 +135,7 @@ public class TextSequenceUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static WordWrapContext wordWrap(final WordWrapContext context,
|
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 word = context.getWord();
|
||||||
TextFragment moreToWrap = null;
|
TextFragment moreToWrap = null;
|
||||||
float indentation = context.getIndentation();
|
float indentation = context.getIndentation();
|
||||||
|
@ -270,9 +256,8 @@ public class TextSequenceUtil {
|
||||||
*
|
*
|
||||||
* @param text the text to de-wrap.
|
* @param text the text to de-wrap.
|
||||||
* @return the de-wrapped text.
|
* @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();
|
TextFlow result = new TextFlow();
|
||||||
for (TextFragment fragment : text) {
|
for (TextFragment fragment : text) {
|
||||||
if (fragment instanceof WrappingNewLine) {
|
if (fragment instanceof WrappingNewLine) {
|
||||||
|
@ -297,13 +282,10 @@ public class TextSequenceUtil {
|
||||||
* @param text the text to word-wrap.
|
* @param text the text to word-wrap.
|
||||||
* @param maxWidth the max width to fit.
|
* @param maxWidth the max width to fit.
|
||||||
* @return the word-wrapped text lines.
|
* @return the word-wrapped text lines.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static List<TextLine> wordWrapToLines(final TextSequence text,
|
public static List<TextLine> wordWrapToLines(final TextSequence text,
|
||||||
final float maxWidth) throws IOException {
|
final float maxWidth) {
|
||||||
TextFlow wrapped = wordWrap(text, maxWidth);
|
return getLines(wordWrap(text, maxWidth));
|
||||||
List<TextLine> lines = getLines(wrapped);
|
|
||||||
return lines;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -367,7 +349,7 @@ public class TextSequenceUtil {
|
||||||
|
|
||||||
private static Pair<TextFragment> breakWord(TextFragment word,
|
private static Pair<TextFragment> breakWord(TextFragment word,
|
||||||
float wordWidth, final float remainingLineWidth, float maxWidth,
|
float wordWidth, final float remainingLineWidth, float maxWidth,
|
||||||
boolean breakHard) throws IOException {
|
boolean breakHard) {
|
||||||
|
|
||||||
float leftMargin = 0;
|
float leftMargin = 0;
|
||||||
float rightMargin = 0;
|
float rightMargin = 0;
|
||||||
|
@ -377,8 +359,7 @@ public class TextSequenceUtil {
|
||||||
rightMargin = styledText.getRightMargin();
|
rightMargin = styledText.getRightMargin();
|
||||||
}
|
}
|
||||||
|
|
||||||
Pair<String> brokenWord = WordBreakerFactory.getWorkBreaker()
|
Pair<String> brokenWord = WordBreakerFactory.getWorkBreaker().breakWord(word.getText(), word.getFontDescriptor(),
|
||||||
.breakWord(word.getText(), word.getFontDescriptor(),
|
|
||||||
remainingLineWidth - leftMargin, breakHard);
|
remainingLineWidth - leftMargin, breakHard);
|
||||||
if (brokenWord == null) {
|
if (brokenWord == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -398,10 +379,8 @@ public class TextSequenceUtil {
|
||||||
*
|
*
|
||||||
* @param fontDescriptor font and size.
|
* @param fontDescriptor font and size.
|
||||||
* @return the width of <code>M</code>.
|
* @return the width of <code>M</code>.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static float getEmWidth(final FontDescriptor fontDescriptor)
|
public static float getEmWidth(final FontDescriptor fontDescriptor) {
|
||||||
throws IOException {
|
|
||||||
return getStringWidth("M", fontDescriptor);
|
return getStringWidth("M", fontDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,12 +390,13 @@ public class TextSequenceUtil {
|
||||||
* @param text the text to measure.
|
* @param text the text to measure.
|
||||||
* @param fontDescriptor font and size.
|
* @param fontDescriptor font and size.
|
||||||
* @return the width of given text.
|
* @return the width of given text.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static float getStringWidth(final String text,
|
public static float getStringWidth(String text, FontDescriptor fontDescriptor) {
|
||||||
final FontDescriptor fontDescriptor) throws IOException {
|
try {
|
||||||
return fontDescriptor.getSize()
|
return fontDescriptor.getSize() * fontDescriptor.getSelectedFont().getStringWidth(text) / 1000;
|
||||||
* fontDescriptor.getFont().getStringWidth(text) / 1000;
|
} catch (IOException exception) {
|
||||||
|
throw new UncheckedIOException(exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -434,13 +414,13 @@ public class TextSequenceUtil {
|
||||||
* @param lineSpacing the line spacing factor.
|
* @param lineSpacing the line spacing factor.
|
||||||
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
||||||
* line also. Makes sense in most cases to do so.
|
* line also. Makes sense in most cases to do so.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static void drawText(TextSequence text,
|
public static void drawText(TextSequence text,
|
||||||
PDPageContentStream contentStream, Position upperLeft,
|
PDPageContentStream contentStream,
|
||||||
DrawListener drawListener, Alignment alignment, float maxWidth,
|
Position upperLeft,
|
||||||
final float lineSpacing, final boolean applyLineSpacingToFirstLine)
|
DrawListener drawListener,
|
||||||
throws IOException {
|
Alignment alignment,
|
||||||
|
float maxWidth, final float lineSpacing, final boolean applyLineSpacingToFirstLine) {
|
||||||
List<TextLine> lines = wordWrapToLines(text, maxWidth);
|
List<TextLine> lines = wordWrapToLines(text, maxWidth);
|
||||||
float maxLineWidth = Math.max(maxWidth, getMaxWidth(lines));
|
float maxLineWidth = Math.max(maxWidth, getMaxWidth(lines));
|
||||||
Position position = upperLeft;
|
Position position = upperLeft;
|
||||||
|
@ -468,11 +448,9 @@ public class TextSequenceUtil {
|
||||||
* @param targetWidth the target width
|
* @param targetWidth the target width
|
||||||
* @param alignment the alignment of the line.
|
* @param alignment the alignment of the line.
|
||||||
* @return the left offset.
|
* @return the left offset.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static float getOffset(final TextSequence textLine,
|
public static float getOffset(final TextSequence textLine,
|
||||||
final float targetWidth, final Alignment alignment)
|
final float targetWidth, final Alignment alignment) {
|
||||||
throws IOException {
|
|
||||||
switch (alignment) {
|
switch (alignment) {
|
||||||
case RIGHT:
|
case RIGHT:
|
||||||
return targetWidth - textLine.getWidth();
|
return targetWidth - textLine.getWidth();
|
||||||
|
@ -488,10 +466,8 @@ public class TextSequenceUtil {
|
||||||
*
|
*
|
||||||
* @param lines the lines for which to calculate the max width.
|
* @param lines the lines for which to calculate the max width.
|
||||||
* @return the max width of the lines.
|
* @return the max width of the lines.
|
||||||
* @throws IOException by pdfbox.
|
|
||||||
*/
|
*/
|
||||||
public static float getMaxWidth(final Iterable<TextLine> lines)
|
public static float getMaxWidth(Iterable<TextLine> lines) {
|
||||||
throws IOException {
|
|
||||||
float max = 0;
|
float max = 0;
|
||||||
for (TextLine line : lines) {
|
for (TextLine line : lines) {
|
||||||
max = Math.max(max, line.getWidth());
|
max = Math.max(max, line.getWidth());
|
||||||
|
@ -505,10 +481,8 @@ public class TextSequenceUtil {
|
||||||
* @param textSequence the text.
|
* @param textSequence the text.
|
||||||
* @param maxWidth if > 0, the text may be word-wrapped to match the width.
|
* @param maxWidth if > 0, the text may be word-wrapped to match the width.
|
||||||
* @return the width of the text.
|
* @return the width of the text.
|
||||||
* @throws IOException by pdfbox.
|
|
||||||
*/
|
*/
|
||||||
public static float getWidth(final TextSequence textSequence,
|
public static float getWidth(TextSequence textSequence, float maxWidth) {
|
||||||
final float maxWidth) throws IOException {
|
|
||||||
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
||||||
float max = 0;
|
float max = 0;
|
||||||
for (TextLine line : lines) {
|
for (TextLine line : lines) {
|
||||||
|
@ -526,11 +500,10 @@ public class TextSequenceUtil {
|
||||||
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
* @param applyLineSpacingToFirstLine indicates if the line spacing should be applied to the first
|
||||||
* line also. Makes sense in most cases to do so.
|
* line also. Makes sense in most cases to do so.
|
||||||
* @return the height of the text.
|
* @return the height of the text.
|
||||||
* @throws IOException by pdfbox
|
|
||||||
*/
|
*/
|
||||||
public static float getHeight(final TextSequence textSequence,
|
public static float getHeight(final TextSequence textSequence,
|
||||||
final float maxWidth, final float lineSpacing,
|
final float maxWidth, final float lineSpacing,
|
||||||
final boolean applyLineSpacingToFirstLine) throws IOException {
|
final boolean applyLineSpacingToFirstLine) {
|
||||||
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
List<TextLine> lines = wordWrapToLines(textSequence, maxWidth);
|
||||||
float sum = 0;
|
float sum = 0;
|
||||||
for (int i = 0; i < lines.size(); i++) {
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
@ -546,9 +519,13 @@ public class TextSequenceUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class WordWrapContext {
|
private static class WordWrapContext {
|
||||||
|
|
||||||
private final TextFragment word;
|
private final TextFragment word;
|
||||||
|
|
||||||
private final float lineLength;
|
private final float lineLength;
|
||||||
|
|
||||||
private final float indentation;
|
private final float indentation;
|
||||||
|
|
||||||
boolean isWrappedLine;
|
boolean isWrappedLine;
|
||||||
|
|
||||||
public WordWrapContext(TextFragment word, float lineLength,
|
public WordWrapContext(TextFragment word, float lineLength,
|
||||||
|
@ -578,6 +555,5 @@ public class TextSequenceUtil {
|
||||||
public boolean isMoreToWrap() {
|
public boolean isMoreToWrap() {
|
||||||
return getWord() != null;
|
return getWord() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.xbib.graphics.pdfbox.layout.text;
|
package org.xbib.graphics.pdfbox.layout.text;
|
||||||
|
|
||||||
|
|
||||||
import org.xbib.graphics.pdfbox.layout.font.FontDescriptor;
|
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 {
|
public class WrappingNewLine extends NewLine {
|
||||||
|
|
||||||
/**
|
|
||||||
* See {@link NewLine#NewLine()}.
|
|
||||||
*/
|
|
||||||
public WrappingNewLine() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link NewLine#NewLine(FontDescriptor)}.
|
* See {@link NewLine#NewLine(FontDescriptor)}.
|
||||||
*
|
*
|
||||||
|
@ -24,15 +16,4 @@ public class WrappingNewLine extends NewLine {
|
||||||
public WrappingNewLine(FontDescriptor fontDescriptor) {
|
public WrappingNewLine(FontDescriptor fontDescriptor) {
|
||||||
super(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<>();
|
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.
|
* Creates a styled text.
|
||||||
*
|
*
|
||||||
|
@ -52,8 +35,7 @@ public class AnnotatedStyledText extends StyledText implements Annotated {
|
||||||
final float rightMargin,
|
final float rightMargin,
|
||||||
final float baselineOffset,
|
final float baselineOffset,
|
||||||
Collection<? extends Annotation> annotations) {
|
Collection<? extends Annotation> annotations) {
|
||||||
super(text, fontDescriptor, color, baselineOffset, leftMargin,
|
super(text, fontDescriptor, color, baselineOffset, leftMargin, rightMargin);
|
||||||
rightMargin);
|
|
||||||
if (annotations != null) {
|
if (annotations != null) {
|
||||||
this.annotations.addAll(annotations);
|
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;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle;
|
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation.LinkStyle;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.UnderlineAnnotation;
|
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.UnderlineAnnotation;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import java.util.regex.Pattern;
|
||||||
*/
|
*/
|
||||||
public class AnnotationCharacters {
|
public class AnnotationCharacters {
|
||||||
|
|
||||||
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new CopyOnWriteArrayList<>();
|
private final static List<AnnotationControlCharacterFactory<?>> FACTORIES = new ArrayList<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
register(new HyperlinkControlCharacterFactory());
|
register(new HyperlinkControlCharacterFactory());
|
||||||
|
@ -30,8 +30,7 @@ public class AnnotationCharacters {
|
||||||
*
|
*
|
||||||
* @param factory the factory to register.
|
* @param factory the factory to register.
|
||||||
*/
|
*/
|
||||||
public static void register(
|
public static void register(AnnotationControlCharacterFactory<?> factory) {
|
||||||
final AnnotationControlCharacterFactory<?> factory) {
|
|
||||||
FACTORIES.add(factory);
|
FACTORIES.add(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +96,7 @@ public class AnnotationCharacters {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String unescape(String text) {
|
public String unescape(String text) {
|
||||||
return text
|
return text.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||||
.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -119,8 +117,7 @@ public class AnnotationCharacters {
|
||||||
@Override
|
@Override
|
||||||
public UnderlineControlCharacter createControlCharacter(String text,
|
public UnderlineControlCharacter createControlCharacter(String text,
|
||||||
Matcher matcher, final List<CharSequence> charactersSoFar) {
|
Matcher matcher, final List<CharSequence> charactersSoFar) {
|
||||||
return new UnderlineControlCharacter(matcher.group(4),
|
return new UnderlineControlCharacter(matcher.group(4), matcher.group(6));
|
||||||
matcher.group(6));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -130,8 +127,7 @@ public class AnnotationCharacters {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String unescape(String text) {
|
public String unescape(String text) {
|
||||||
return text
|
return text.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
||||||
.replaceAll("\\\\" + Pattern.quote(TO_ESCAPE), TO_ESCAPE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -149,6 +145,7 @@ public class AnnotationCharacters {
|
||||||
*/
|
*/
|
||||||
public static class HyperlinkControlCharacter extends
|
public static class HyperlinkControlCharacter extends
|
||||||
AnnotationControlCharacter<HyperlinkAnnotation> {
|
AnnotationControlCharacter<HyperlinkAnnotation> {
|
||||||
|
|
||||||
private HyperlinkAnnotation hyperlink;
|
private HyperlinkAnnotation hyperlink;
|
||||||
|
|
||||||
protected HyperlinkControlCharacter(final String 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.DrawListener;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.DrawableText;
|
import org.xbib.graphics.pdfbox.layout.text.DrawableText;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This listener has to be passed to all
|
* This listener has to be passed to all
|
||||||
|
@ -44,59 +43,35 @@ public class AnnotationDrawListener implements DrawListener, RenderListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||||
try {
|
annotationProcessor.annotatedObjectDrawn(
|
||||||
annotationProcessor.annotatedObjectDrawn(
|
(Annotated) drawnObject, drawContext, upperLeft, width,
|
||||||
(Annotated) drawnObject, drawContext, upperLeft, width,
|
height);
|
||||||
height);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"exception on annotation processing", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IOException by pdfbox.
|
|
||||||
* @deprecated user {@link #afterRender()} instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void finalizeAnnotations() throws IOException {
|
public void finalizeAnnotations() {
|
||||||
afterRender();
|
afterRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforePage(RenderContext renderContext) throws IOException {
|
public void beforePage(RenderContext renderContext) {
|
||||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||||
try {
|
annotationProcessor.beforePage(drawContext);
|
||||||
annotationProcessor.beforePage(drawContext);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(
|
|
||||||
"exception on annotation processing", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPage(RenderContext renderContext) throws IOException {
|
public void afterPage(RenderContext renderContext) {
|
||||||
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||||
try {
|
annotationProcessor.afterPage(drawContext);
|
||||||
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) {
|
for (AnnotationProcessor annotationProcessor : annotationProcessors) {
|
||||||
try {
|
annotationProcessor.afterRender(drawContext.getPdDocument());
|
||||||
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.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
import org.xbib.graphics.pdfbox.layout.text.Position;
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes an annotation.
|
* Processes an annotation.
|
||||||
|
@ -18,34 +17,30 @@ public interface AnnotationProcessor {
|
||||||
* @param upperLeft the upper left position the object has been drawn to.
|
* @param upperLeft the upper left position the object has been drawn to.
|
||||||
* @param width the width of the drawn object.
|
* @param width the width of the drawn object.
|
||||||
* @param height the height of the drawn object.
|
* @param height the height of the drawn object.
|
||||||
* @throws IOException by pdfbox.
|
|
||||||
*/
|
*/
|
||||||
void annotatedObjectDrawn(final Annotated drawnObject,
|
void annotatedObjectDrawn(final Annotated drawnObject,
|
||||||
final DrawContext drawContext, Position upperLeft, float width,
|
final DrawContext drawContext, Position upperLeft, float width,
|
||||||
float height) throws IOException;
|
float height);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before a page is drawn.
|
* Called before a page is drawn.
|
||||||
*
|
*
|
||||||
* @param drawContext the drawing context.
|
* @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.
|
* Called after a page is drawn.
|
||||||
*
|
*
|
||||||
* @param drawContext the drawing context.
|
* @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.
|
* Called after all rendering has been performed.
|
||||||
*
|
*
|
||||||
* @param document the document.
|
* @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.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
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.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.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
import org.xbib.graphics.pdfbox.layout.text.DrawContext;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.Position;
|
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.AnchorAnnotation;
|
||||||
import org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.HyperlinkAnnotation;
|
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.HyperlinkAnnotation.LinkStyle;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.CompatibilityHelper;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,13 +33,13 @@ import java.util.Map.Entry;
|
||||||
*/
|
*/
|
||||||
public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||||
|
|
||||||
private final Map<String, PageAnchor> anchorMap = new HashMap<String, PageAnchor>();
|
private final Map<String, PageAnchor> anchorMap = new HashMap<>();
|
||||||
private final Map<PDPage, List<Hyperlink>> linkMap = new HashMap<PDPage, List<Hyperlink>>();
|
private final Map<PDPage, List<Hyperlink>> linkMap = new HashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void annotatedObjectDrawn(Annotated drawnObject,
|
public void annotatedObjectDrawn(Annotated drawnObject,
|
||||||
DrawContext drawContext, Position upperLeft, float width,
|
DrawContext drawContext, Position upperLeft, float width,
|
||||||
float height) throws IOException {
|
float height) {
|
||||||
|
|
||||||
if (!(drawnObject instanceof AnnotatedStyledText)) {
|
if (!(drawnObject instanceof AnnotatedStyledText)) {
|
||||||
return;
|
return;
|
||||||
|
@ -85,26 +92,107 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRender(PDDocument document) throws IOException {
|
public void afterRender(PDDocument document) {
|
||||||
for (Entry<PDPage, List<Hyperlink>> entry : linkMap.entrySet()) {
|
for (Entry<PDPage, List<Hyperlink>> entry : linkMap.entrySet()) {
|
||||||
PDPage page = entry.getKey();
|
PDPage page = entry.getKey();
|
||||||
List<Hyperlink> links = entry.getValue();
|
List<Hyperlink> links = entry.getValue();
|
||||||
for (Hyperlink hyperlink : links) {
|
for (Hyperlink hyperlink : links) {
|
||||||
PDAnnotationLink pdLink = null;
|
PDAnnotationLink pdLink;
|
||||||
if (hyperlink.getHyperlinkURI().startsWith("#")) {
|
if (hyperlink.getHyperlinkURI().startsWith("#")) {
|
||||||
pdLink = createGotoLink(hyperlink);
|
pdLink = createGotoLink(hyperlink);
|
||||||
} else {
|
} else {
|
||||||
pdLink = CompatibilityHelper.createLink(page,
|
pdLink = createLink(page,
|
||||||
hyperlink.getRect(), hyperlink.getColor(),
|
hyperlink.getRect(), hyperlink.getColor(),
|
||||||
hyperlink.getLinkStyle(),
|
hyperlink.getLinkStyle(),
|
||||||
hyperlink.getHyperlinkURI());
|
hyperlink.getHyperlinkURI());
|
||||||
}
|
}
|
||||||
page.getAnnotations().add(pdLink);
|
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) {
|
private PDAnnotationLink createGotoLink(Hyperlink hyperlink) {
|
||||||
String anchor = hyperlink.getHyperlinkURI().substring(1);
|
String anchor = hyperlink.getHyperlinkURI().substring(1);
|
||||||
PageAnchor pageAnchor = anchorMap.get(anchor);
|
PageAnchor pageAnchor = anchorMap.get(anchor);
|
||||||
|
@ -116,7 +204,7 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||||
xyzDestination.setPage(pageAnchor.getPage());
|
xyzDestination.setPage(pageAnchor.getPage());
|
||||||
xyzDestination.setLeft((int) pageAnchor.getX());
|
xyzDestination.setLeft((int) pageAnchor.getX());
|
||||||
xyzDestination.setTop((int) pageAnchor.getY());
|
xyzDestination.setTop((int) pageAnchor.getY());
|
||||||
return CompatibilityHelper.createLink(pageAnchor.getPage(), hyperlink.getRect(),
|
return createLink(pageAnchor.getPage(), hyperlink.getRect(),
|
||||||
hyperlink.getColor(), hyperlink.getLinkStyle(), xyzDestination);
|
hyperlink.getColor(), hyperlink.getLinkStyle(), xyzDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +235,6 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PageAnchor [page=" + page + ", x=" + x + ", y=" + y + "]";
|
return "PageAnchor [page=" + page + ", x=" + x + ", y=" + y + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Hyperlink {
|
private static class Hyperlink {
|
||||||
|
@ -186,7 +273,5 @@ public class HyperlinkAnnotationProcessor implements AnnotationProcessor {
|
||||||
+ ", hyperlinkUri=" + hyperlinkUri + ", linkStyle="
|
+ ", hyperlinkUri=" + hyperlinkUri + ", linkStyle="
|
||||||
+ 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 org.xbib.graphics.pdfbox.layout.text.annotations.Annotations.UnderlineAnnotation;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.
|
* the needed hyperlink metadata to the PDF document.
|
||||||
*/
|
*/
|
||||||
public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||||
|
@ -22,23 +23,20 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void annotatedObjectDrawn(Annotated drawnObject,
|
public void annotatedObjectDrawn(Annotated drawnObject,
|
||||||
DrawContext drawContext, Position upperLeft, float width,
|
DrawContext drawContext,
|
||||||
|
Position upperLeft,
|
||||||
|
float width,
|
||||||
float height) {
|
float height) {
|
||||||
if (!(drawnObject instanceof StyledText)) {
|
if (!(drawnObject instanceof StyledText)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StyledText drawnText = (StyledText) drawnObject;
|
StyledText drawnText = (StyledText) drawnObject;
|
||||||
for (UnderlineAnnotation underlineAnnotation : drawnObject
|
for (UnderlineAnnotation underlineAnnotation : drawnObject.getAnnotationsOfType(UnderlineAnnotation.class)) {
|
||||||
.getAnnotationsOfType(UnderlineAnnotation.class)) {
|
|
||||||
float fontSize = drawnText.getFontDescriptor().getSize();
|
float fontSize = drawnText.getFontDescriptor().getSize();
|
||||||
float ascent = fontSize
|
float ascent = fontSize * drawnText.getFontDescriptor().getSelectedFont().getFontDescriptor().getAscent() / 1000;
|
||||||
* drawnText.getFontDescriptor().getFont()
|
|
||||||
.getFontDescriptor().getAscent() / 1000;
|
|
||||||
float baselineOffset = fontSize * underlineAnnotation.getBaselineOffsetScale();
|
float baselineOffset = fontSize * underlineAnnotation.getBaselineOffsetScale();
|
||||||
float thickness = (0.01f + fontSize * 0.05f)
|
float thickness = (0.01f + fontSize * 0.05f) * underlineAnnotation.getLineWeight();
|
||||||
* underlineAnnotation.getLineWeight();
|
Position start = new Position(upperLeft.getX(), upperLeft.getY() - ascent + baselineOffset);
|
||||||
Position start = new Position(upperLeft.getX(), upperLeft.getY()
|
|
||||||
- ascent + baselineOffset);
|
|
||||||
Position end = new Position(start.getX() + width, start.getY());
|
Position end = new Position(start.getX() + width, start.getY());
|
||||||
Stroke stroke = Stroke.builder().lineWidth(thickness).build();
|
Stroke stroke = Stroke.builder().lineWidth(thickness).build();
|
||||||
Line line = new Line(start, end, stroke, drawnText.getColor());
|
Line line = new Line(start, end, stroke, drawnText.getColor());
|
||||||
|
@ -47,12 +45,12 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforePage(DrawContext drawContext) throws IOException {
|
public void beforePage(DrawContext drawContext) {
|
||||||
linesOnPage.clear();
|
linesOnPage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterPage(DrawContext drawContext) throws IOException {
|
public void afterPage(DrawContext drawContext) {
|
||||||
for (Line line : linesOnPage) {
|
for (Line line : linesOnPage) {
|
||||||
line.draw(drawContext.getCurrentPageContentStream());
|
line.draw(drawContext.getCurrentPageContentStream());
|
||||||
}
|
}
|
||||||
|
@ -60,15 +58,18 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRender(PDDocument document) throws IOException {
|
public void afterRender(PDDocument document) {
|
||||||
linesOnPage.clear();
|
linesOnPage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Line {
|
private static class Line {
|
||||||
|
|
||||||
private final Position start;
|
private final Position start;
|
||||||
|
|
||||||
private final Position end;
|
private final Position end;
|
||||||
|
|
||||||
private final Stroke stroke;
|
private final Stroke stroke;
|
||||||
|
|
||||||
private final Color color;
|
private final Color color;
|
||||||
|
|
||||||
public Line(Position start, Position end, Stroke stroke, Color color) {
|
public Line(Position start, Position end, Stroke stroke, Color color) {
|
||||||
|
@ -79,15 +80,19 @@ public class UnderlineAnnotationProcessor implements AnnotationProcessor {
|
||||||
this.color = color;
|
this.color = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void draw(PDPageContentStream contentStream) throws IOException {
|
public void draw(PDPageContentStream contentStream) {
|
||||||
if (color != null) {
|
try {
|
||||||
contentStream.setStrokingColor(color);
|
if (color != null) {
|
||||||
|
contentStream.setStrokingColor(color);
|
||||||
|
}
|
||||||
|
if (stroke != null) {
|
||||||
|
stroke.applyTo(contentStream);
|
||||||
|
}
|
||||||
|
contentStream.moveTo(start.getX(), start.getY());
|
||||||
|
contentStream.lineTo(end.getX(), end.getY());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
if (stroke != null) {
|
|
||||||
stroke.applyTo(contentStream);
|
|
||||||
}
|
|
||||||
contentStream.moveTo(start.getX(), start.getY());
|
|
||||||
contentStream.lineTo(end.getX(), end.getY());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.LowerCaseAlphabeticEnumerator;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.Enumerators.LowerCaseRomanEnumerator;
|
import org.xbib.graphics.pdfbox.layout.util.Enumerators.LowerCaseRomanEnumerator;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.Enumerators.RomanEnumerator;
|
import org.xbib.graphics.pdfbox.layout.util.Enumerators.RomanEnumerator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enumerators are created using this factory. It allows you to register and use
|
* 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 {
|
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 {
|
static {
|
||||||
register("1", ArabicEnumerator.class);
|
register("1", ArabicEnumerator.class);
|
||||||
|
|
|
@ -23,6 +23,6 @@ public interface WordBreaker {
|
||||||
*/
|
*/
|
||||||
Pair<String> breakWord(String word,
|
Pair<String> breakWord(String word,
|
||||||
FontDescriptor fontDescriptor, float maxWidth,
|
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.DefaultWordBreaker;
|
||||||
import org.xbib.graphics.pdfbox.layout.util.WordBreakers.NonBreakingWordBreaker;
|
import org.xbib.graphics.pdfbox.layout.util.WordBreakers.NonBreakingWordBreaker;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for creating a {@link WordBreaker}. This may be used to define a
|
* 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";
|
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.
|
* class name of the (legacy) non-breaking word breaker.
|
||||||
*/
|
*/
|
||||||
|
@ -34,7 +28,8 @@ public class WordBreakerFactory {
|
||||||
.getName();
|
.getName();
|
||||||
|
|
||||||
private final static WordBreaker DEFAULT_WORD_BREAKER = new DefaultWordBreaker();
|
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.
|
* @return the word breaker instance to use.
|
||||||
|
@ -59,8 +54,7 @@ public class WordBreakerFactory {
|
||||||
try {
|
try {
|
||||||
return (WordBreaker) Class.forName(className).getDeclaredConstructor().newInstance();
|
return (WordBreaker) Class.forName(className).getDeclaredConstructor().newInstance();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(String.format(
|
throw new RuntimeException(String.format("failed to create word breaker '%s'", className), e);
|
||||||
"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