From 2c96544543d02e22988c79ff37272382b5fe3acb Mon Sep 17 00:00:00 2001 From: Summer Boot Framework Date: Sat, 31 Oct 2026 13:04:44 -0400 Subject: [PATCH 1/4] PdfBuilder: removed PostOffice parameter, replaced with SinglePageHeightCalculationListener.onHeightCalculationRetry() to let caller decide what to do when retry happened. --- CHANGES.md | 1 + pom.xml | 7 +- .../jexpress/integration/pdf/PdfBuilder.java | 88 +++++++++++++------ 3 files changed, 65 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9d799dbb..71bf1aac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ of error reporting by ensuring that only meaningful references are included in the ServiceError object. - REF275-1: Enforce JJWT to use Jackson 3 - RpcDelegate and RpcResult: refactoring +- PdfBuilder: removed **`PostOffice`** parameter, replaced with **`SinglePageHeightCalculationListener.onHeightCalculationRetry()`** to let caller decide what to do when retry happened. ## Version 2.7.4 (2026-10-24) diff --git a/pom.xml b/pom.xml index abfd650d..d8baffcd 100644 --- a/pom.xml +++ b/pom.xml @@ -193,12 +193,12 @@ 33.7.2-jre 4.36.2 - 2.2.55 + 3.0.0 - 4.1.0 + 4.2.0 4.0.0 3.0.0 @@ -876,6 +876,9 @@ ${maven-compiler.version} true + + -parameters + diff --git a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java index 4b35a707..cbad102c 100644 --- a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java +++ b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java @@ -19,7 +19,6 @@ import com.google.protobuf.ByteString; import com.openhtmltopdf.util.XRLog; -import freemarker.template.Template; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -28,9 +27,7 @@ import org.summerboot.jexpress.api.common.BootPoi; import org.summerboot.jexpress.api.common.Err; import org.summerboot.jexpress.api.common.SessionContext; -import org.summerboot.jexpress.api.mail.PostOffice; import org.summerboot.jexpress.boot.BootConstants; -import org.summerboot.jexpress.integration.mail.config.SmtpClientConfig; import org.summerboot.jexpress.util.format.PageCssUtil; import java.io.File; @@ -40,9 +37,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.zip.CRC32; /** @@ -65,22 +60,27 @@ public static PdfBuilder init(File templateDir, File fontDir, File fontCacheDir, return pdfBuilder; } - public final File dumpDir; - - public boolean isDumpEnabled() { - return BootConstants.isDebugMode() && dumpDir.exists(); + public static interface SinglePageHeightCalculationListener { + /** + * + * @param retry + * @param pageCount + * @param requesterTxId + * @param htmlContent + * @param agent + * @param extraSpace + * @param pageHeightMillimeters + */ + void onHeightCalculationRetry(int retry, int pageCount, String requesterTxId, String htmlContent, PdfBuilderConfig.Agent agent, int extraSpace, float pageHeightMillimeters); } - private File htmlTemplateDir; - + public final File dumpDir; + private final File htmlTemplateDir; + protected final PdfBoxAgent pdfBoxAgent; + protected final ItextAgent agentIText; + protected final List listeners = new ArrayList<>(); //private static final WriterProperties WRITER_PROPS = IText.buildDefaultAccessPermission(null, null, true, PdfVersion.PDF_1_7); - private final Map FreeMarkerTemplates = new HashMap<>(); - - protected PdfBoxAgent pdfBoxAgent; - protected ItextAgent agentIText; - - /** * Initializes the PDF builder with template and font directories. * @@ -113,21 +113,44 @@ protected PdfBuilder(File htmlTemplateDir, File fontDir, File fontCacheDir, File this.dumpDir = dumpDir; } - public byte[] html2PDF(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, PostOffice po, SessionContext context) throws IOException { - return html2PDF(requesterTxId, htmlContent, false, cfg, po, context); + public File getDumpDir() { + return dumpDir; } - public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, PdfBuilderConfig cfg, PostOffice po, SessionContext context) throws IOException { - return html2PDF(requesterTxId, htmlContent, isSinglePage, 5, cfg, po, context); + public File getHtmlTemplateDir() { + return htmlTemplateDir; + } + + public void addListener(SinglePageHeightCalculationListener listener) { + if (listener == null) { + return; + } + listeners.add(listener); } - public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, int extraSpace, PdfBuilderConfig cfg, PostOffice po, SessionContext context) throws IOException { + public boolean removeListener(SinglePageHeightCalculationListener listener) { + return listeners.remove(listener); + } + + public boolean isDumpEnabled() { + return BootConstants.isDebugMode() && dumpDir.exists(); + } + + public byte[] html2PDF(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, SessionContext context) throws IOException { + return html2PDF(requesterTxId, htmlContent, false, cfg, context); + } + + public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, PdfBuilderConfig cfg, SessionContext context) throws IOException { + return html2PDF(requesterTxId, htmlContent, isSinglePage, 5, cfg, context); + } + + public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, int extraSpace, PdfBuilderConfig cfg, SessionContext context) throws IOException { context.poi(BootPoi.PDF_BEGIN); PdfBuilderConfig.Agent agent = cfg.getAgent(); if (extraSpace < 1) { extraSpace = 5; } - final String sessionName = requesterTxId + "_" + agent + "_" + isSinglePage + "_" + extraSpace; + final String sessionName = requesterTxId + "[" + agent + "]" + (isSinglePage ? "SP" : "A4") + "+" + extraSpace + "mm"; byte[] pdf = null; try { if (isSinglePage) { @@ -144,15 +167,23 @@ public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSingl htmlTemplate = PageCssUtil.setHeight(htmlContent, pageHeightMillimeters + "mm;"); layoutInfo = pdfBoxAgent.layoutThenGetInfo(htmlTemplate, htmlTemplateDir); pageCount = layoutInfo.getPageCount(); + context.poi(BootPoi.PDF_HV) + .memo("retry=" + retry + ",pages=" + pageCount + ", extraSpace=" + extraSpace + "mm, height=" + pageHeightMillimeters + "mm", Level.TRACE); retry++; - context.poi(BootPoi.PDF_HV); } if (retry > 1) { - context.level(Level.WARN).memo("template height oversize retry = " + retry).error(new Err(0, null, null, null, sessionName + " template height oversize retry = " + retry)); - //log.fatal("template height oversize (retry={}) for TxId={}", retry, sessionName); - if (po != null) { - po.sendAlertAsync(SmtpClientConfig.cfg.getEmailToDevelopment(), "HTML oversize @" + sessionName, "retry=" + retry, null, true); + context.level(Level.WARN) + .memo("template height oversize retry = " + retry) + .error(new Err(0, null, null, null, sessionName + " template height oversize retry = " + retry)); + for (SinglePageHeightCalculationListener listener : listeners) { + if (listener == null) { + continue; + } + listener.onHeightCalculationRetry(retry, pageCount, requesterTxId, htmlContent, agent, extraSpace, pageHeightMillimeters); } +// if (po != null) { +// po.sendAlertAsync(SmtpClientConfig.cfg.getEmailToDevelopment(), "HTML oversize @" + sessionName, "retry=" + retry, null, true); +// } } htmlContent = htmlTemplate; } @@ -192,7 +223,6 @@ public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSingl return pdf; } - public List pdf2Images(String requesterTxId, byte[] pdf, String password, ImageType imageType, float imageDPI, String imageFormat, RenderDestination renderDestination, SessionContext context) throws IOException { context.poi(BootPoi.PDF2IMG_BEGIN); String sessionName = requesterTxId + "_" + imageType + "_" + renderDestination + "_" + imageDPI + "_" + imageFormat; From 8e5e787e3a8ced1bd09799295430563038358164 Mon Sep 17 00:00:00 2001 From: Summer Boot Framework Date: Sat, 31 Oct 2026 14:36:47 -0400 Subject: [PATCH 2/4] max retry can be defined by caller via PdfBuilderConfig.setSinglePageHeightCalculationMaxIteration(int), the default value is 3; it was previously hardcoded to 2 --- CHANGES.md | 7 ++- pom.xml | 24 ++++---- .../jexpress/integration/pdf/PdfBuilder.java | 60 +++++++++++++------ .../integration/pdf/PdfBuilderConfig.java | 9 +++ 4 files changed, 69 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 71bf1aac..ac30d652 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,12 @@ of error reporting by ensuring that only meaningful references are included in the ServiceError object. - REF275-1: Enforce JJWT to use Jackson 3 - RpcDelegate and RpcResult: refactoring -- PdfBuilder: removed **`PostOffice`** parameter, replaced with **`SinglePageHeightCalculationListener.onHeightCalculationRetry()`** to let caller decide what to do when retry happened. +- PdfBuilder: extra space for single page PDF + - removed **`PostOffice`** parameter, replaced with **`SinglePageHeightCalculationListener.onHeightCalculationRetry()`** to let caller decide what to do when retry happened. + - Multiple **`SinglePageHeightCalculationListener`** can be added via **`PdfBuilder.addListener()`** + - Multiple **`SinglePageHeightCalculationListener`** can be removed via **`PdfBuilder.removeListener()`** + - A one time **`SinglePageHeightCalculationListener`** can be provided via **`htmlToPdfSinglePage(...)`** to override added listeners + - max retry can be defined by caller via **`PdfBuilderConfig.setSinglePageHeightCalculationMaxIteration(int)`**, the default value is 3; it was previously hardcoded to 2. ## Version 2.7.4 (2026-10-24) diff --git a/pom.xml b/pom.xml index d8baffcd..ceef8fcc 100644 --- a/pom.xml +++ b/pom.xml @@ -80,10 +80,10 @@ 2.2.1 3.4.0 3.12.0 - 3.1.4 + 3.2.0 3.2.8 1.7.0 - 0.10.0 + 0.12.0 @@ -161,13 +161,13 @@ 21 3.5.0 - 3.15.0 - 3.10.0 - 3.5.0 + 3.16.0 + 3.11.0 + 3.5.1 3.5.0 - 3.5.5 - 3.12.0 - 3.1.4 + 3.6.1 + 3.12.0 + 3.2.0 3.21.0 1.11.0 @@ -877,11 +877,9 @@ true - -parameters + -parameters + - - - @@ -925,7 +923,7 @@ org.apache.maven.plugins maven-javadoc-plugin - ${maven-doc-version} + ${maven-javadoc-version} --allow-script-in-comments diff --git a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java index cbad102c..e3757c25 100644 --- a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java +++ b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java @@ -136,15 +136,37 @@ public boolean isDumpEnabled() { return BootConstants.isDebugMode() && dumpDir.exists(); } - public byte[] html2PDF(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, SessionContext context) throws IOException { - return html2PDF(requesterTxId, htmlContent, false, cfg, context); + /** + * Build multipage PDF + * + * @param requesterTxId + * @param htmlContent + * @param cfg + * @param context + * @return + * @throws IOException + */ + public byte[] htmlToPdfMultiPage(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, SessionContext context) throws IOException { + return htmlToPdf(requesterTxId, htmlContent, cfg, false, 0, null, context); } - public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, PdfBuilderConfig cfg, SessionContext context) throws IOException { - return html2PDF(requesterTxId, htmlContent, isSinglePage, 5, cfg, context); + /** + * Build single page PDF + * + * @param requesterTxId + * @param htmlContent + * @param cfg + * @param extraSpace + * @param onetimeListener + * @param context + * @return + * @throws IOException + */ + public byte[] htmlToPdfSinglePage(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, int extraSpace, SinglePageHeightCalculationListener onetimeListener, SessionContext context) throws IOException { + return htmlToPdf(requesterTxId, htmlContent, cfg, true, extraSpace, onetimeListener, context); } - public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSinglePage, int extraSpace, PdfBuilderConfig cfg, SessionContext context) throws IOException { + protected byte[] htmlToPdf(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, boolean isSinglePage, int extraSpace, SinglePageHeightCalculationListener onetimeListener, SessionContext context) throws IOException { context.poi(BootPoi.PDF_BEGIN); PdfBuilderConfig.Agent agent = cfg.getAgent(); if (extraSpace < 1) { @@ -161,28 +183,32 @@ public byte[] html2PDF(String requesterTxId, String htmlContent, boolean isSingl int pageCount = layoutInfo.getPageCount(); float pageHeightMillimeters = layoutInfo.getPageHeight() * pageCount / PdfBoxAgent.POINTS_PER_MM; String htmlTemplate = htmlContent; - int retry = 0; - while (pageCount > 1 && retry < 2) { + int iterationCount = 0; + while (pageCount > 1 && iterationCount < cfg.getSinglePageHeightCalculationMaxIteration()) { pageHeightMillimeters += extraSpace;//add extra space htmlTemplate = PageCssUtil.setHeight(htmlContent, pageHeightMillimeters + "mm;"); layoutInfo = pdfBoxAgent.layoutThenGetInfo(htmlTemplate, htmlTemplateDir); pageCount = layoutInfo.getPageCount(); + iterationCount++; context.poi(BootPoi.PDF_HV) - .memo("retry=" + retry + ",pages=" + pageCount + ", extraSpace=" + extraSpace + "mm, height=" + pageHeightMillimeters + "mm", Level.TRACE); - retry++; + .memo("iterationCount=" + iterationCount + ",pages=" + pageCount + ", extraSpace=" + extraSpace + "mm, height=" + pageHeightMillimeters + "mm"); } - if (retry > 1) { + if (iterationCount > 1) { context.level(Level.WARN) - .memo("template height oversize retry = " + retry) - .error(new Err(0, null, null, null, sessionName + " template height oversize retry = " + retry)); - for (SinglePageHeightCalculationListener listener : listeners) { - if (listener == null) { - continue; + .memo("template height oversize iterationCount = " + iterationCount) + .error(new Err(0, null, null, null, sessionName + " template height oversize iterationCount = " + iterationCount)); + if (onetimeListener == null) { + for (SinglePageHeightCalculationListener addedListener : listeners) { + if (addedListener == null) { + continue; + } + addedListener.onHeightCalculationRetry(iterationCount, pageCount, requesterTxId, htmlContent, agent, extraSpace, pageHeightMillimeters); } - listener.onHeightCalculationRetry(retry, pageCount, requesterTxId, htmlContent, agent, extraSpace, pageHeightMillimeters); + } else { + onetimeListener.onHeightCalculationRetry(iterationCount, pageCount, requesterTxId, htmlContent, agent, extraSpace, pageHeightMillimeters); } // if (po != null) { -// po.sendAlertAsync(SmtpClientConfig.cfg.getEmailToDevelopment(), "HTML oversize @" + sessionName, "retry=" + retry, null, true); +// po.sendAlertAsync(SmtpClientConfig.cfg.getEmailToDevelopment(), "HTML oversize @" + sessionName, "iterationCount=" + iterationCount, null, true); // } } htmlContent = htmlTemplate; diff --git a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilderConfig.java b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilderConfig.java index ef9c14ac..0fdb926a 100644 --- a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilderConfig.java +++ b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilderConfig.java @@ -49,6 +49,7 @@ public static final PdfBuilderConfig buildUnprotectedConfig() { private String userPwd = null; private int encryptionKeyLength = ProtectionSpec.EncryptionKeyLength; // PDFBox private int encryptionAlgorithm = ProtectionSpec.EncryptionAlgorithm; // iText + private int singlePageHeightCalculationMaxIteration = 3; public PdfBuilderConfig() { this(null); @@ -168,4 +169,12 @@ public int getEncryptionAlgorithm() { public void setEncryptionAlgorithm(int encryptionAlgorithm) { this.encryptionAlgorithm = encryptionAlgorithm; } + + public int getSinglePageHeightCalculationMaxIteration() { + return singlePageHeightCalculationMaxIteration; + } + + public void setSinglePageHeightCalculationMaxIteration(int singlePageHeightCalculationMaxIteration) { + this.singlePageHeightCalculationMaxIteration = singlePageHeightCalculationMaxIteration; + } } From 9fff113614845a73bf4c67b697fb0c98937d4b7b Mon Sep 17 00:00:00 2001 From: Summer Boot Framework Date: Sat, 31 Oct 2026 14:43:17 -0400 Subject: [PATCH 3/4] max retry can be defined by caller via PdfBuilderConfig.setSinglePageHeightCalculationMaxIteration(int), the default value is 3; it was previously hardcoded to 2 --- .../org/summerboot/jexpress/integration/pdf/PdfBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java index e3757c25..07e11678 100644 --- a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java +++ b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java @@ -172,7 +172,7 @@ protected byte[] htmlToPdf(String requesterTxId, String htmlContent, PdfBuilderC if (extraSpace < 1) { extraSpace = 5; } - final String sessionName = requesterTxId + "[" + agent + "]" + (isSinglePage ? "SP" : "A4") + "+" + extraSpace + "mm"; + final String sessionName = requesterTxId + "[" + agent + "]" + (isSinglePage ? ("SP+" + extraSpace + "mm") : "MP"); byte[] pdf = null; try { if (isSinglePage) { From 821a1de580f0056f7cb60166a3f03ecef7750d44 Mon Sep 17 00:00:00 2001 From: Summer Boot Framework Date: Sat, 31 Oct 2026 16:17:12 -0400 Subject: [PATCH 4/4] max retry can be defined by caller via PdfBuilderConfig.setSinglePageHeightCalculationMaxIteration(int), the default value is 3; it was previously hardcoded to 2 --- CHANGES.md | 2 +- pom.xml | 2 -- .../summerboot/jexpress/integration/pdf/PdfBuilder.java | 8 ++++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac30d652..c36aa3a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,7 +10,7 @@ - removed **`PostOffice`** parameter, replaced with **`SinglePageHeightCalculationListener.onHeightCalculationRetry()`** to let caller decide what to do when retry happened. - Multiple **`SinglePageHeightCalculationListener`** can be added via **`PdfBuilder.addListener()`** - Multiple **`SinglePageHeightCalculationListener`** can be removed via **`PdfBuilder.removeListener()`** - - A one time **`SinglePageHeightCalculationListener`** can be provided via **`htmlToPdfSinglePage(...)`** to override added listeners + - A one time **`SinglePageHeightCalculationListener`** can be provided via **`htmlToPdf(...)`** to override added listeners - max retry can be defined by caller via **`PdfBuilderConfig.setSinglePageHeightCalculationMaxIteration(int)`**, the default value is 3; it was previously hardcoded to 2. ## Version 2.7.4 (2026-10-24) diff --git a/pom.xml b/pom.xml index ceef8fcc..14e42f61 100644 --- a/pom.xml +++ b/pom.xml @@ -157,8 +157,6 @@ UTF-8 - - 21 3.5.0 3.16.0 diff --git a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java index 07e11678..c684f354 100644 --- a/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java +++ b/src/main/java/org/summerboot/jexpress/integration/pdf/PdfBuilder.java @@ -137,7 +137,7 @@ public boolean isDumpEnabled() { } /** - * Build multipage PDF + * Build a multipage PDF * * @param requesterTxId * @param htmlContent @@ -146,12 +146,12 @@ public boolean isDumpEnabled() { * @return * @throws IOException */ - public byte[] htmlToPdfMultiPage(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, SessionContext context) throws IOException { + public byte[] htmlToPdf(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, SessionContext context) throws IOException { return htmlToPdf(requesterTxId, htmlContent, cfg, false, 0, null, context); } /** - * Build single page PDF + * Build a single page PDF * * @param requesterTxId * @param htmlContent @@ -162,7 +162,7 @@ public byte[] htmlToPdfMultiPage(String requesterTxId, String htmlContent, PdfBu * @return * @throws IOException */ - public byte[] htmlToPdfSinglePage(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, int extraSpace, SinglePageHeightCalculationListener onetimeListener, SessionContext context) throws IOException { + public byte[] htmlToPdf(String requesterTxId, String htmlContent, PdfBuilderConfig cfg, int extraSpace, SinglePageHeightCalculationListener onetimeListener, SessionContext context) throws IOException { return htmlToPdf(requesterTxId, htmlContent, cfg, true, extraSpace, onetimeListener, context); }