Last active
October 1, 2019 18:56
-
-
Save ecmel/3204aa6a9aa30a0b5649 to your computer and use it in GitHub Desktop.
PDFBox 2 Text Helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package portal.core; | |
import java.awt.Color; | |
import java.io.Closeable; | |
import java.io.IOException; | |
import java.util.ArrayDeque; | |
import java.util.Deque; | |
import org.apache.pdfbox.cos.COSDictionary; | |
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.font.PDFont; | |
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; | |
import org.apache.pdfbox.pdmodel.graphics.image.PDInlineImage; | |
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading; | |
import org.apache.pdfbox.pdmodel.interactive.viewerpreferences.PDViewerPreferences; | |
import org.apache.pdfbox.util.Matrix; | |
/** | |
* | |
* @author Ecmel Ercan | |
*/ | |
public final class PDFlowStream implements Closeable { | |
private final static float magic = 0.551784f; | |
private final Deque<State> states = new ArrayDeque<>(); | |
private final PDDocument document; | |
private PDPage page; | |
private PDPageContentStream stream; | |
private Repeater repeater; | |
private float xpos = 0.0f; | |
private float ypos = 0.0f; | |
private float top = cm(2.0f); | |
private float right = cm(2.0f); | |
private float bottom = cm(2.0f); | |
private float left = cm(2.0f); | |
private float fontSize = 10.0f; | |
private PDFont font = null; | |
private float indent = 0.0f; | |
private Color strokingColor = Color.BLACK; | |
private Color nonStrokingColor = Color.BLACK; | |
public PDFlowStream(PDDocument document) | |
{ | |
PDViewerPreferences prefs = new PDViewerPreferences(new COSDictionary()); | |
prefs.setPrintScaling(PDViewerPreferences.PRINT_SCALING.None); | |
document.getDocumentCatalog().setViewerPreferences(prefs); | |
this.document = document; | |
} | |
private void reset() | |
{ | |
indent = 0.0f; | |
strokingColor = Color.BLACK; | |
nonStrokingColor = Color.BLACK; | |
} | |
public float cm(float cm) | |
{ | |
return cm * 72 / 2.54f; | |
} | |
private float pt(float f) | |
{ | |
return f / 1000 * fontSize; | |
} | |
public PDDocument document() | |
{ | |
return document; | |
} | |
public PDPage page() | |
{ | |
return page; | |
} | |
public float pageWidth() | |
{ | |
return page.getMediaBox().getWidth(); | |
} | |
public float pageHeight() | |
{ | |
return page.getMediaBox().getHeight(); | |
} | |
public float width() | |
{ | |
return pageWidth() - left - right; | |
} | |
public float height() | |
{ | |
return pageHeight() - top - bottom; | |
} | |
public float top() | |
{ | |
return top; | |
} | |
public float right() | |
{ | |
return right; | |
} | |
public float bottom() | |
{ | |
return bottom; | |
} | |
public float left() | |
{ | |
return left; | |
} | |
public float indent() | |
{ | |
return indent; | |
} | |
public float ypos() | |
{ | |
return ypos; | |
} | |
public float indentWidth() | |
{ | |
return width() - indent; | |
} | |
public float charWidth(int c) throws IOException | |
{ | |
return textWidth("W") * c; | |
} | |
public float textWidth(String text) throws IOException | |
{ | |
return pt(font.getStringWidth(text)); | |
} | |
public float fontCap() | |
{ | |
return pt(font.getFontDescriptor().getCapHeight()); | |
} | |
public float fontAscent() | |
{ | |
return pt(font.getFontDescriptor().getAscent()); | |
} | |
public float fontDescent() | |
{ | |
return pt(font.getFontDescriptor().getDescent()); | |
} | |
public float fontHeight() | |
{ | |
return pt(font.getFontDescriptor().getFontBoundingBox().getHeight()); | |
} | |
public PDFlowStream font(PDFont font) throws IOException | |
{ | |
if (stream != null) { | |
stream.setFont(font, fontSize); | |
} | |
this.font = font; | |
return this; | |
} | |
public PDFlowStream fontSize(float fontSize) throws IOException | |
{ | |
if (stream != null && font != null) { | |
stream.setFont(font, fontSize); | |
} | |
this.fontSize = fontSize; | |
return this; | |
} | |
public PDFlowStream top(float top) | |
{ | |
this.top = top; | |
return this; | |
} | |
public PDFlowStream right(float right) | |
{ | |
this.right = right; | |
return this; | |
} | |
public PDFlowStream bottom(float bottom) | |
{ | |
this.bottom = bottom; | |
return this; | |
} | |
public PDFlowStream left(float left) | |
{ | |
this.left = left; | |
return this; | |
} | |
public PDFlowStream repeater(Repeater repeater) | |
{ | |
this.repeater = repeater; | |
return this; | |
} | |
public PDFlowStream newPage() throws IOException | |
{ | |
return newPage(page == null ? PDRectangle.A4 : page.getMediaBox()); | |
} | |
public PDFlowStream newPage(PDRectangle mediaBox) throws IOException | |
{ | |
if (stream != null) { | |
close(); | |
} | |
page = new PDPage(mediaBox); | |
document.addPage(page); | |
stream = new PDPageContentStream(document, page); | |
stream.setFont(font, fontSize); | |
stream.setStrokingColor(strokingColor); | |
stream.setNonStrokingColor(nonStrokingColor); | |
if (repeater != null) { | |
saveState(); | |
reset(); | |
repeater.page(this); | |
beginText(); | |
stream.newLineAtOffset(left, pageHeight() - top); | |
repeater.header(this); | |
endText(); | |
restoreState(); | |
} | |
beginText(); | |
newBreakAt(0); | |
return this; | |
} | |
public PDFlowStream newBreakAt(int line) throws IOException | |
{ | |
ypos = pageHeight() - top - fontAscent() - fontHeight() * line; | |
if (ypos < bottom) { | |
newPage(); | |
} else { | |
indent(indent); | |
} | |
return this; | |
} | |
public PDFlowStream newBreak() throws IOException | |
{ | |
ypos -= fontHeight(); | |
if (ypos < bottom) { | |
newPage(); | |
} else { | |
newLine(); | |
} | |
return this; | |
} | |
public PDFlowStream newBreak(int count) throws IOException | |
{ | |
for (int i = 0; i < count; i++) { | |
newBreak(); | |
} | |
return this; | |
} | |
public PDFlowStream newLine(int count) throws IOException | |
{ | |
xpos = 0.0f; | |
stream.newLineAtOffset(0.0f, -fontHeight() * count); | |
return this; | |
} | |
public PDFlowStream newLine() throws IOException | |
{ | |
return newLine(1); | |
} | |
public PDFlowStream indent(float indent) throws IOException | |
{ | |
xpos = 0.0f; | |
transformText(Matrix.getTranslateInstance(left + indent, ypos)); | |
this.indent = indent; | |
return this; | |
} | |
public PDFlowStream showWords(String text, float width) throws IOException | |
{ | |
return showWords(text.split("(?<=\\s+)"), width); | |
} | |
public PDFlowStream showWords(String text) throws IOException | |
{ | |
return showWords(text, width()); | |
} | |
private PDFlowStream showWords(String[] words, float width) throws IOException | |
{ | |
float w; | |
for (String word : words) { | |
w = textWidth(word); | |
if (w > width) { | |
showWords(word.split(""), width); | |
} else { | |
xpos += w; | |
if (xpos > width) { | |
newBreak(); | |
xpos = w; | |
} | |
stream.showText(word); | |
} | |
} | |
return this; | |
} | |
public PDFlowStream showTextCenter(String text) throws IOException | |
{ | |
return showText(((indent == 0 ? width() : 0) - textWidth(text)) / 2, text); | |
} | |
public PDFlowStream showTextRight(String text) throws IOException | |
{ | |
return showText((indent == 0 ? width() : 0) - textWidth(text), text); | |
} | |
private PDFlowStream showText(float tx, String text) throws IOException | |
{ | |
stream.newLineAtOffset(tx, 0); | |
stream.showText(text); | |
stream.newLineAtOffset(-tx, 0); | |
return this; | |
} | |
public PDFlowStream showTextCircle(float x, float y, float r, double theta, String text) throws IOException | |
{ | |
String[] ch = text.split(""); | |
Matrix m; | |
float w; | |
for (int i = 0; i < ch.length; i++) { | |
w = textWidth(ch[i]); | |
m = Matrix.getTranslateInstance(x, y); | |
m.rotate(theta); | |
m.translate(-w / 2, r); | |
transformText(m); | |
showText(ch[i]); | |
if (i < (ch.length - 1)) { | |
theta -= Math.atan((w + textWidth(ch[i + 1])) / 2 / r); | |
} | |
} | |
return this; | |
} | |
public PDFlowStream addEllipse(float x, float y, float xr, float yr) throws IOException | |
{ | |
float xm = xr * magic; | |
float ym = yr * magic; | |
stream.moveTo(x - xr, y); | |
stream.curveTo(x - xr, y + ym, x - xm, y + yr, x, y + yr); | |
stream.curveTo(x + xm, y + yr, x + xr, y + ym, x + xr, y); | |
stream.curveTo(x + xr, y - ym, x + xm, y - yr, x, y - yr); | |
stream.curveTo(x - xm, y - yr, x - xr, y - ym, x - xr, y); | |
return this; | |
} | |
public PDFlowStream addCircle(float x, float y, float r) throws IOException | |
{ | |
return addEllipse(x, y, r, r); | |
} | |
public PDFlowStream strokingColor(Color color) throws IOException | |
{ | |
stream.setStrokingColor(color); | |
strokingColor = color; | |
return this; | |
} | |
public PDFlowStream nonStrokingColor(Color color) throws IOException | |
{ | |
stream.setNonStrokingColor(color); | |
nonStrokingColor = color; | |
return this; | |
} | |
public PDFlowStream saveState() throws IOException | |
{ | |
stream.saveGraphicsState(); | |
states.push(new State(this)); | |
return this; | |
} | |
public PDFlowStream restoreState() throws IOException | |
{ | |
stream.restoreGraphicsState(); | |
states.pop().restore(this); | |
return this; | |
} | |
public PDFlowStream beginBlock() throws IOException | |
{ | |
endText(); | |
saveState(); | |
return this; | |
} | |
public PDFlowStream endBlock() throws IOException | |
{ | |
restoreState(); | |
beginText(); | |
indent(indent); | |
return this; | |
} | |
public PDFlowStream beginText() throws IOException | |
{ | |
stream.beginText(); | |
return this; | |
} | |
public PDFlowStream endText() throws IOException | |
{ | |
stream.endText(); | |
return this; | |
} | |
public PDFlowStream showText(String text) throws IOException | |
{ | |
stream.showText(text); | |
return this; | |
} | |
public PDFlowStream transformText(Matrix matrix) throws IOException | |
{ | |
stream.setTextMatrix(matrix); | |
return this; | |
} | |
public PDFlowStream transform(Matrix matrix) throws IOException | |
{ | |
stream.transform(matrix); | |
return this; | |
} | |
public PDFlowStream lineWidth(float lineWidth) throws IOException | |
{ | |
stream.setLineWidth(lineWidth); | |
return this; | |
} | |
public PDFlowStream lineJoinStyle(JoinStyle style) throws IOException | |
{ | |
stream.setLineJoinStyle(style.ordinal()); | |
return this; | |
} | |
public PDFlowStream lineCapStyle(CapStyle style) throws IOException | |
{ | |
stream.setLineCapStyle(style.ordinal()); | |
return this; | |
} | |
public PDFlowStream lineDashPattern(float[] pattern, float phase) throws IOException | |
{ | |
stream.setLineDashPattern(pattern, phase); | |
return this; | |
} | |
public PDFlowStream addRect(float x, float y, float width, float height) throws IOException | |
{ | |
stream.addRect(x, y, width, height); | |
return this; | |
} | |
public PDFlowStream moveTo(float x, float y) throws IOException | |
{ | |
stream.moveTo(x, y); | |
return this; | |
} | |
public PDFlowStream lineTo(float x, float y) throws IOException | |
{ | |
stream.lineTo(x, y); | |
return this; | |
} | |
public PDFlowStream curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException | |
{ | |
stream.curveTo(x1, y1, x2, y2, x3, y3); | |
return this; | |
} | |
public PDFlowStream curveTo2(float x2, float y2, float x3, float y3) throws IOException | |
{ | |
stream.curveTo2(x2, y2, x3, y3); | |
return this; | |
} | |
public PDFlowStream curveTo1(float x1, float y1, float x3, float y3) throws IOException | |
{ | |
stream.curveTo1(x1, y1, x3, y3); | |
return this; | |
} | |
public PDFlowStream closePath() throws IOException | |
{ | |
stream.closePath(); | |
return this; | |
} | |
public PDFlowStream clip() throws IOException | |
{ | |
stream.clip(); | |
return this; | |
} | |
public PDFlowStream clipEvenOdd() throws IOException | |
{ | |
stream.clipEvenOdd(); | |
return this; | |
} | |
public PDFlowStream stroke() throws IOException | |
{ | |
stream.stroke(); | |
return this; | |
} | |
public PDFlowStream fill() throws IOException | |
{ | |
stream.fill(); | |
return this; | |
} | |
public PDFlowStream shadingFill(PDShading shading) throws IOException | |
{ | |
stream.shadingFill(shading); | |
return this; | |
} | |
public PDFlowStream drawImage(PDImageXObject image, float x, float y) throws IOException | |
{ | |
stream.drawImage(image, x, y); | |
return this; | |
} | |
public PDFlowStream drawImage(PDImageXObject image, float x, float y, float width, float height) throws IOException | |
{ | |
stream.drawImage(image, x, y, width, height); | |
return this; | |
} | |
public PDFlowStream drawImage(PDInlineImage image, float x, float y) throws IOException | |
{ | |
stream.drawImage(image, x, y); | |
return this; | |
} | |
public PDFlowStream drawImage(PDInlineImage image, float x, float y, float width, float height) throws IOException | |
{ | |
stream.drawImage(image, x, y, width, height); | |
return this; | |
} | |
@Override | |
public void close() throws IOException | |
{ | |
endText(); | |
if (repeater != null) { | |
saveState(); | |
reset(); | |
beginText(); | |
stream.newLineAtOffset(left, bottom); | |
repeater.footer(this); | |
endText(); | |
restoreState(); | |
} | |
stream.close(); | |
} | |
public static enum JoinStyle { | |
MITER, | |
ROUND, | |
BEVEL | |
} | |
public static enum CapStyle { | |
BUTT, | |
ROUND, | |
SQUARE | |
} | |
private static final class State { | |
private final float xpos; | |
private final float ypos; | |
private final float top; | |
private final float right; | |
private final float bottom; | |
private final float left; | |
private final float fontSize; | |
private final PDFont font; | |
private final float indent; | |
private final Color strokingColor; | |
private final Color nonStrokingColor; | |
State(PDFlowStream fs) throws IOException | |
{ | |
xpos = fs.xpos; | |
ypos = fs.ypos; | |
top = fs.top; | |
right = fs.right; | |
bottom = fs.bottom; | |
left = fs.left; | |
fontSize = fs.fontSize; | |
font = fs.font; | |
indent = fs.indent; | |
strokingColor = fs.strokingColor; | |
nonStrokingColor = fs.nonStrokingColor; | |
} | |
void restore(PDFlowStream fs) throws IOException | |
{ | |
fs.xpos = xpos; | |
fs.ypos = ypos; | |
fs.top = top; | |
fs.right = right; | |
fs.bottom = bottom; | |
fs.left = left; | |
fs.fontSize = fontSize; | |
fs.font = font; | |
fs.indent = indent; | |
fs.strokingColor = strokingColor; | |
fs.nonStrokingColor = nonStrokingColor; | |
} | |
} | |
public static abstract class Repeater { | |
public void page(PDFlowStream fs) throws IOException | |
{ | |
} | |
public void header(PDFlowStream fs) throws IOException | |
{ | |
} | |
public void footer(PDFlowStream fs) throws IOException | |
{ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment