Skip to content

Commit ebee550

Browse files
Bump PDFBox to 3.03 and improve memory usage
Issue:202734
1 parent a7bd809 commit ebee550

File tree

2 files changed

+91
-83
lines changed

2 files changed

+91
-83
lines changed

java/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120
<dependency>
121121
<groupId>org.apache.pdfbox</groupId>
122122
<artifactId>pdfbox</artifactId>
123-
<version>2.0.27</version>
123+
<version>3.0.3</version>
124124
</dependency>
125125
<dependency>
126126
<groupId>org.jsoup</groupId>

java/src/main/java/com/genexus/reports/PDFReportPDFBox.java

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
import java.awt.image.BufferedImage;
55
import java.io.*;
66
import java.net.URL;
7-
import java.util.ArrayList;
8-
import java.util.HashSet;
9-
import java.util.List;
10-
import java.util.Set;
7+
import java.util.*;
118
import java.util.concurrent.ConcurrentHashMap;
129

1310
import com.genexus.ApplicationContext;
@@ -60,12 +57,9 @@ public class PDFReportPDFBox extends GXReportPDFCommons{
6057
ConcurrentHashMap<String, PDImageXObject> documentImages;
6158
public int runDirection = 0;
6259
private int page;
63-
private PDPageContentStream auxContentStream;
64-
private boolean useAuxContentStream;
65-
6660
private final float DEFAULT_PDFBOX_LEADING = 1.2f;
67-
6861
private Set<String> supportedHTMLTags = new HashSet<>();
62+
private PDPageContentStream currentPageContentStream;
6963

7064
static {
7165
log = org.apache.logging.log4j.LogManager.getLogger(PDFReportPDFBox.class);
@@ -87,10 +81,6 @@ protected void init() {
8781
}
8882
}
8983

90-
private PDPageContentStream getNewPDPageContentStream() throws IOException {
91-
return new PDPageContentStream(document, document.getPage(page - 1), PDPageContentStream.AppendMode.APPEND,false);
92-
}
93-
9484
private void drawRectangle(PDPageContentStream cb, float x, float y, float w, float h,
9585
int styleTop, int styleBottom, int styleRight, int styleLeft,
9686
float radioTL, float radioTR, float radioBL, float radioBR, float penAux, boolean hideCorners) {
@@ -229,9 +219,8 @@ private void roundRectangle(PDPageContentStream cb, float x, float y, float w, f
229219

230220
public void GxDrawRect(int left, int top, int right, int bottom, int pen, int foreRed, int foreGreen, int foreBlue, int backMode, int backRed, int backGreen, int backBlue,
231221
int styleTop, int styleBottom, int styleRight, int styleLeft, int cornerRadioTL, int cornerRadioTR, int cornerRadioBL, int cornerRadioBR) {
232-
PDPageContentStream cb = null;
233-
try{
234-
cb = useAuxContentStream ? auxContentStream : getNewPDPageContentStream();
222+
try {
223+
PDPageContentStream cb = currentPageContentStream;
235224
float penAux = (float) convertScale(pen);
236225
float rightAux = (float) convertScale(right);
237226
float bottomAux = (float) convertScale(bottom);
@@ -309,20 +298,11 @@ public void GxDrawRect(int left, int top, int right, int bottom, int pen, int fo
309298
log.debug("GxDrawRect -> (" + left + "," + top + ") - (" + right + "," + bottom + ") BackMode: " + backMode + " Pen:" + pen);
310299
} catch (Exception e) {
311300
log.error("GxDrawRect failed: ", e);
312-
} finally {
313-
try {
314-
if (cb != null && !useAuxContentStream)
315-
cb.close();
316-
else if (useAuxContentStream)
317-
useAuxContentStream = false;
318-
} catch (IOException ioe) {
319-
log.error("Failed to close content stream", ioe);
320-
}
321301
}
322302
}
323303

324304
public void GxDrawLine(int left, int top, int right, int bottom, int width, int foreRed, int foreGreen, int foreBlue, int style) {
325-
try (PDPageContentStream cb = getNewPDPageContentStream()){
305+
try (PDPageContentStream cb = currentPageContentStream){
326306

327307
float widthAux = (float)convertScale(width);
328308
float rightAux = (float)convertScale(right);
@@ -361,7 +341,7 @@ public void GxDrawLine(int left, int top, int right, int bottom, int width, int
361341
}
362342

363343
public void GxDrawBitMap(String bitmap, int left, int top, int right, int bottom, int aspectRatio) {
364-
try (PDPageContentStream cb = getNewPDPageContentStream()){
344+
try (PDPageContentStream cb = currentPageContentStream){
365345
PDImageXObject image;
366346
try {
367347
if (documentImages != null && documentImages.containsKey(bitmap)) {
@@ -427,6 +407,14 @@ public void GxDrawBitMap(String bitmap, int left, int top, int right, int bottom
427407
}
428408
}
429409

410+
private static final int MAX_FONT_CACHE_SIZE = 50;
411+
private final Map<String, PDFont> fontCache = new LinkedHashMap<String, PDFont>(MAX_FONT_CACHE_SIZE, 0.75f, true) {
412+
@Override
413+
protected boolean removeEldestEntry(Map.Entry<String, PDFont> eldest) {
414+
return size() > MAX_FONT_CACHE_SIZE;
415+
}
416+
};
417+
430418
public void GxAttris(String fontName, int fontSize, boolean fontBold, boolean fontItalic, boolean fontUnderline, boolean fontStrikethru, int Pen, int foreRed, int foreGreen, int foreBlue, int backMode, int backRed, int backGreen, int backBlue) {
431419
boolean isCJK = false;
432420
boolean embeedFont = isEmbeddedFont(fontName);
@@ -492,10 +480,8 @@ public void GxAttris(String fontName, int fontSize, boolean fontBold, boolean fo
492480
}
493481
}
494482
baseFont = createPDType1FontFromName(fontName);
495-
if (baseFont != null)
496-
baseFontName = baseFont.getName();
497483
if (baseFont == null){
498-
baseFont = PDType0Font.load(document, new File(getFontLocation(fontName)));
484+
baseFont = getOrLoadFont(fontName, document);
499485
baseFontName = fontName;
500486
}
501487

@@ -516,19 +502,20 @@ public void GxAttris(String fontName, int fontSize, boolean fontBold, boolean fo
516502
String fontPath = getFontLocation(fontName);
517503
boolean foundFont = true;
518504
if (fontPath.equals("")) {
519-
fontPath = PDFFontDescriptor.getTrueTypeFontLocation(fontName, props);
505+
fontName = fontName.endsWith(style) ? fontName.substring(0, fontName.length() - style.length()) : fontName;
506+
fontPath = getFontLocation(fontName);
507+
baseFont = getOrLoadFont(fontName, document);
508+
baseFontName = fontName;
520509
if (fontPath.equals("")) {
521-
baseFont = PDType1Font.HELVETICA;
522-
baseFontName = baseFont.getName();
510+
baseFont = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
511+
baseFontName = "Helvetica";
523512
foundFont = false;
524513
}
525514
}
526515
if (foundFont){
527516
baseFont = createPDType1FontFromName(fontName);
528-
if (baseFont != null)
529-
baseFontName = baseFont.getName();
530-
else{
531-
baseFont = PDType0Font.load(document, new File(getFontLocation(fontName)));
517+
if (baseFont == null){
518+
baseFont = getOrLoadFont(fontName, document);
532519
baseFontName = fontName;
533520
}
534521
}
@@ -542,68 +529,79 @@ public void GxAttris(String fontName, int fontSize, boolean fontBold, boolean fo
542529
private static PDType1Font createPDType1FontFromName(String fontName) {
543530
switch (fontName) {
544531
case "Times-Roman":
545-
return PDType1Font.TIMES_ROMAN;
532+
return new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
546533
case "Times-Bold":
547-
return PDType1Font.TIMES_BOLD;
534+
return new PDType1Font(Standard14Fonts.FontName.TIMES_BOLD);
548535
case "Times-Italic":
549-
return PDType1Font.TIMES_ITALIC;
536+
return new PDType1Font(Standard14Fonts.FontName.TIMES_ITALIC);
550537
case "Times-BoldItalic":
551-
return PDType1Font.TIMES_BOLD_ITALIC;
538+
return new PDType1Font(Standard14Fonts.FontName.TIMES_BOLD_ITALIC);
552539
case "Helvetica":
553-
return PDType1Font.HELVETICA;
540+
return new PDType1Font(Standard14Fonts.FontName.HELVETICA);
554541
case "Helvetica-Bold":
555-
return PDType1Font.HELVETICA_BOLD;
542+
return new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD);
556543
case "Helvetica-Oblique":
557-
return PDType1Font.HELVETICA_OBLIQUE;
544+
return new PDType1Font(Standard14Fonts.FontName.HELVETICA_OBLIQUE);
558545
case "Helvetica-BoldOblique":
559-
return PDType1Font.HELVETICA_BOLD_OBLIQUE;
546+
return new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD_OBLIQUE);
560547
case "Courier":
561-
return PDType1Font.COURIER;
548+
return new PDType1Font(Standard14Fonts.FontName.COURIER);
562549
case "Courier-Bold":
563-
return PDType1Font.COURIER_BOLD;
550+
return new PDType1Font(Standard14Fonts.FontName.COURIER_BOLD);
564551
case "Courier-Oblique":
565-
return PDType1Font.COURIER_OBLIQUE;
552+
return new PDType1Font(Standard14Fonts.FontName.COURIER_OBLIQUE);
566553
case "Courier-BoldOblique":
567-
return PDType1Font.COURIER_BOLD_OBLIQUE;
554+
return new PDType1Font(Standard14Fonts.FontName.COURIER_BOLD_OBLIQUE);
568555
case "Symbol":
569-
return PDType1Font.SYMBOL;
556+
return new PDType1Font(Standard14Fonts.FontName.SYMBOL);
570557
case "ZapfDingbats":
571-
return PDType1Font.ZAPF_DINGBATS;
558+
return new PDType1Font(Standard14Fonts.FontName.ZAPF_DINGBATS);
572559
default:
573560
return null;
574561
}
575562
}
576563

577564
public void setAsianFont(String fontName, String style) {
578565
try {
579-
String fontPath = getFontLocation(fontName);
580-
baseFont = PDType0Font.load(document, new File(fontPath));
581-
baseFontName = fontName;
566+
baseFont = getOrLoadFont(fontName, document);
582567
}
583568
catch(Exception e) {
584569
log.error("setAsianFont failed: ", e);
585570
}
586571
}
587572

573+
private PDFont getOrLoadFont(String fontName, PDDocument doc) throws IOException {
574+
PDFont cachedFont = fontCache.get(fontName);
575+
if (cachedFont != null) {
576+
return cachedFont;
577+
}
578+
PDFont font = createPDType1FontFromName(fontName);
579+
if (font == null) {
580+
String fontPath = getFontLocation(fontName);
581+
if (!fontPath.isEmpty()) {
582+
font = PDType0Font.load(doc, new File(fontPath));
583+
} else {
584+
font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);
585+
}
586+
}
587+
fontCache.put(fontName, font);
588+
return font;
589+
}
590+
588591
public void GxDrawText(String sTxt, int left, int top, int right, int bottom, int align, int htmlformat, int border, int valign) {
589-
PDPageContentStream cb = null;
590592
try {
591-
cb = getNewPDPageContentStream();
593+
PDPageContentStream cb = currentPageContentStream;
592594
boolean printRectangle = false;
593595
if (props.getBooleanGeneralProperty(Const.BACK_FILL_IN_CONTROLS, true))
594596
printRectangle = true;
595597

596598
if (printRectangle && (border == 1 || backFill)) {
597-
auxContentStream = cb;
598-
useAuxContentStream = true;
599599
GxDrawRect(left, top, right, bottom, border, foreColor.getRed(), foreColor.getGreen(), foreColor.getBlue(), backFill ? 1 : 0, backColor.getRed(), backColor.getGreen(), backColor.getBlue(), 0, 0);
600600
}
601601

602602
sTxt = CommonUtil.rtrim(sTxt);
603603

604-
PDFont font = createPDType1FontFromName(baseFont.getFontDescriptor().getFontName());
605-
if (font == null)
606-
font = PDType0Font.load(document, new File(getFontLocation(baseFontName)));
604+
PDFont font = getOrLoadFont(baseFontName, document);
607605

608606
cb.setFont(font,fontSize);
609607
cb.setNonStrokingColor(foreColor);
@@ -668,8 +666,7 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
668666
GxEndPage();
669667
GxStartPage();
670668

671-
cb.close();
672-
cb = getNewPDPageContentStream();
669+
cb = currentPageContentStream;
673670
}
674671
if (this.supportedHTMLTags.contains(element.normalName()))
675672
processHTMLElement(cb, htmlRectangle, spaceHandler, element);
@@ -755,6 +752,7 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
755752
cb.setNonStrokingColor(backColor);
756753
cb.addRect(rectangle.getLowerLeftX(), rectangle.getLowerLeftY(),rectangle.getWidth(), rectangle.getHeight());
757754
cb.fill();
755+
cb.setNonStrokingColor(foreColor);
758756
}
759757

760758
float underlineSeparation = lineHeight / 5;
@@ -784,11 +782,11 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
784782
underline.setUpperRightY(this.pageSize.getUpperRightY() - bottomAux - topMargin -bottomMargin + startHeight - underlineHeight);
785783
break;
786784
}
787-
PDPageContentStream contentStream = getNewPDPageContentStream();
785+
PDPageContentStream contentStream = currentPageContentStream;
788786
contentStream.setNonStrokingColor(foreColor);
789787
contentStream.addRect(underline.getLowerLeftX(), underline.getLowerLeftY(),underline.getWidth(), underline.getHeight());
790788
contentStream.fill();
791-
contentStream.close();
789+
cb.setNonStrokingColor(foreColor);
792790
}
793791

794792
if (fontStrikethru) {
@@ -815,11 +813,10 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
815813
underline.setUpperRightY(this.pageSize.getUpperRightY() - bottomAux - topMargin -bottomMargin + startHeight - underlineHeight + strikethruSeparation);
816814
break;
817815
}
818-
PDPageContentStream contentStream = getNewPDPageContentStream();
816+
PDPageContentStream contentStream = currentPageContentStream;
819817
contentStream.setNonStrokingColor(foreColor);
820818
contentStream.addRect(underline.getLowerLeftX(), underline.getLowerLeftY() - strikethruSeparation * 1/3, underline.getWidth(), underline.getHeight());
821819
contentStream.fill();
822-
contentStream.close();
823820
}
824821

825822
if(sTxt.trim().equalsIgnoreCase("{{Pages}}")) {
@@ -877,13 +874,6 @@ else if (valign == PDFReportPDFBox.VerticalAlign.BOTTOM.value())
877874
}
878875
} catch (Exception ioe){
879876
log.error("GxDrawText failed: ", ioe);
880-
} finally {
881-
try {
882-
if (cb != null) cb.close();
883-
}
884-
catch (IOException ioe) {
885-
log.error("GxDrawText failed to close a content stream to one of it's pages: ", ioe);
886-
}
887877
}
888878
}
889879

@@ -903,7 +893,7 @@ private void loadSupportedHTMLTags(){
903893
private void processHTMLElement(PDPageContentStream cb, PDRectangle htmlRectangle, SpaceHandler spaceHandler, Element blockElement) throws Exception{
904894
this.fontBold = false;
905895
String tagName = blockElement.normalName();
906-
PDFont htmlFont = PDType1Font.TIMES_ROMAN;
896+
PDFont htmlFont = new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
907897

908898
if (tagName.equals("div") || tagName.equals("span")) {
909899
for (Node child : blockElement.childNodes())
@@ -916,7 +906,7 @@ private void processHTMLElement(PDPageContentStream cb, PDRectangle htmlRectangl
916906
return;
917907
}
918908

919-
float lineHeight = (PDType1Font.TIMES_ROMAN.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize) * DEFAULT_PDFBOX_LEADING;
909+
float lineHeight = (new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN).getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize) * DEFAULT_PDFBOX_LEADING;
920910
float leading = (float)(Double.valueOf(props.getGeneralProperty(Const.LEADING)).doubleValue());
921911

922912
float llx = htmlRectangle.getLowerLeftX();
@@ -1040,7 +1030,7 @@ public void setAvailableSpace(float availableSpace) {
10401030

10411031
private float renderHTMLContent(PDPageContentStream contentStream, String text, float fontSize, float llx, float lly, float urx, float ury) {
10421032
try {
1043-
PDFont defaultHTMLFont = PDType1Font.TIMES_ROMAN;
1033+
PDFont defaultHTMLFont = new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
10441034
List<String> lines = new ArrayList<>();
10451035
String[] words = text.split(" ");
10461036
StringBuilder currentLine = new StringBuilder();
@@ -1093,18 +1083,18 @@ private void resolveTextStyling(PDPageContentStream contentStream, String text,
10931083
contentStream.setLineWidth(fontSize * 0.05f);
10941084
contentStream.setRenderingMode(RenderingMode.FILL_STROKE);
10951085
contentStream.beginText();
1096-
contentStream.moveTextPositionByAmount(x, y);
1086+
contentStream.newLineAtOffset(x, y);
10971087
contentStream.setTextMatrix(new Matrix(1, 0, 0.2f, 1, x + 0.2f * y, y));
10981088
contentStream.newLineAtOffset(-0.2f * y, 0);
10991089
} else if (this.fontBold && !this.fontItalic){
11001090
contentStream.setStrokingColor(foreColor);
11011091
contentStream.setLineWidth(fontSize * 0.05f);
11021092
contentStream.setRenderingMode(RenderingMode.FILL_STROKE);
11031093
contentStream.beginText();
1104-
contentStream.moveTextPositionByAmount(x, y);
1094+
contentStream.newLineAtOffset(x, y);
11051095
} else if (!this.fontBold && this.fontItalic){
11061096
contentStream.beginText();
1107-
contentStream.moveTextPositionByAmount(x, y);
1097+
contentStream.newLineAtOffset(x, y);
11081098
contentStream.setTextMatrix(new Matrix(1, 0, 0.2f, 1, x + 0.2f * y, y));
11091099
contentStream.newLineAtOffset(-0.2f * y, 0);
11101100
} else {
@@ -1372,10 +1362,28 @@ public void GxEndDocument() {
13721362
log.error("GxEndDocument failed: ", e);;
13731363
}
13741364
}
1365+
13751366
public void GxStartPage() {
1376-
document.addPage(new PDPage(this.pageSize));
1377-
pages = pages + 1;
1378-
page = page + 1;
1367+
PDPage page = new PDPage(this.pageSize);
1368+
document.addPage(page);
1369+
pages++;
1370+
this.page++;
1371+
try {
1372+
currentPageContentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false);
1373+
} catch (IOException e) {
1374+
log.error("Failed to start page content stream: ", e);
1375+
}
1376+
}
1377+
1378+
public void GxEndPage() {
1379+
try {
1380+
if (currentPageContentStream != null) {
1381+
currentPageContentStream.close();
1382+
currentPageContentStream = null;
1383+
}
1384+
} catch (IOException e) {
1385+
log.error("Failed to close page content stream: ", e);
1386+
}
13791387
}
13801388

13811389
}

0 commit comments

Comments
 (0)