@@ -10,14 +10,17 @@ import com.typesafe.config.Config
1010import com.typesafe.config.ConfigFactory
1111
1212import groovy.json.JsonOutput
13+ import org.apache.commons.io.output.TeeOutputStream
1314import org.apache.pdfbox.pdmodel.PDDocument
1415import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo
1516import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination
1617import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink
1718
19+ import java.nio.channels.SeekableByteChannel
1820import java.nio.file.Files
1921import java.nio.file.Path
2022import java.nio.file.Paths
23+ import java.nio.file.StandardOpenOption
2124import java.time.Duration
2225
2326import org.apache.commons.io.FileUtils
@@ -64,8 +67,8 @@ class DocGen implements Jooby.Module {
6467 }
6568
6669 // Generate a PDF document for a combination of template type, version and data
67- def byte [] generate (String type , String version , Object data ) {
68- def result = []
70+ File generate (String type , String version , Object data ) {
71+ File resultFile = null
6972 def tmpDir = null
7073
7174 try {
@@ -88,7 +91,7 @@ class DocGen implements Jooby.Module {
8891 }
8992
9093 // Convert the exected templates into a PDF document
91- result = Util . convertHtmlToPDF(partials. document, partials. header, partials. footer, data)
94+ resultFile = Util . convertHtmlToPDF(partials. document, partials. header, partials. footer, data)
9295 } catch (Throwable e) {
9396 throw e
9497 } finally {
@@ -97,7 +100,7 @@ class DocGen implements Jooby.Module {
97100 }
98101 }
99102
100- return result
103+ return resultFile
101104 }
102105
103106 // Read partial templates for a template type and version from the basePath directory
@@ -143,80 +146,81 @@ class DocGen implements Jooby.Module {
143146 }
144147
145148 // Convert a HTML document, with an optional header and footer, into a PDF
146- static private def byte [] convertHtmlToPDF (Path documentHtmlFile , Path headerHtmlFile = null , Path footerHtmlFile = null , Object data ) {
147- def documentPDFFile = null
149+ static private File convertHtmlToPDF (Path documentHtmlFile , Path headerHtmlFile = null , Path footerHtmlFile = null , Object data ) {
150+ def documentPDFFilePath = Files . createTempFile( " document " , " .pdf " )
148151
149- try {
150- documentPDFFile = Files . createTempFile(" document" , " .pdf" )
152+ def cmd = [" wkhtmltopdf" , " --encoding" , " UTF-8" , " --no-outline" , " --print-media-type" ]
153+ cmd << " --enable-local-file-access"
154+ cmd. addAll([" -T" , " 40" , " -R" , " 25" , " -B" , " 25" , " -L" , " 25" ])
151155
152- def cmd = [" wkhtmltopdf" , " --encoding" , " UTF-8" , " --no-outline" , " --print-media-type" ]
153- cmd << " --enable-local-file-access"
154- cmd. addAll([" -T" , " 40" , " -R" , " 25" , " -B" , " 25" , " -L" , " 25" ])
155-
156- if (data?. metadata?. header) {
157- if (data. metadata. header. size() > 1 ) {
158- cmd. addAll([" --header-center" , """ ${ data.metadata.header[0]}
156+ if (data?. metadata?. header) {
157+ if (data. metadata. header. size() > 1 ) {
158+ cmd. addAll([" --header-center" , """ ${ data.metadata.header[0]}
159159${ data.metadata.header[1]} """ ])
160- } else {
161- cmd. addAll([" --header-center" , data. metadata. header[0 ]])
162- }
163-
164- cmd. addAll([" --header-font-size" , " 10" , " --header-spacing" , " 10" ])
160+ } else {
161+ cmd. addAll([" --header-center" , data. metadata. header[0 ]])
165162 }
166163
167- cmd. addAll([" --footer-center" , " 'Page [page] of [topage]'" , " --footer-font-size" , " 10" ])
164+ cmd. addAll([" --header-font-size" , " 10" , " --header-spacing" , " 10" ])
165+ }
168166
169- if (data?. metadata?. orientation) {
170- cmd. addAll([" --orientation" , data. metadata. orientation])
171- }
167+ cmd. addAll([" --footer-center" , " 'Page [page] of [topage]'" , " --footer-font-size" , " 10" ])
172168
173- cmd << documentHtmlFile. toFile(). absolutePath
174- cmd << documentPDFFile. toFile(). absolutePath
169+ if (data?. metadata?. orientation) {
170+ cmd. addAll([" --orientation" , data. metadata. orientation])
171+ }
172+
173+ cmd << documentHtmlFile. toFile(). absolutePath
174+ cmd << documentPDFFilePath. toFile(). absolutePath
175175
176- println " [INFO]: executing cmd: ${ cmd} "
177- def result = Util . shell(cmd)
176+ println " [INFO]: executing cmd: ${ cmd} "
177+
178+ def result = Util . shell(cmd)
179+ try {
178180 if (result. rc != 0 ) {
181+ String stderr = result. stderr. text
179182 println " [ERROR]: ${ cmd} has exited with code ${ result.rc} "
180- println " [ERROR]: ${ result. stderr} "
183+ println " [ERROR]: ${ stderr} "
181184 throw new IllegalStateException (
182- " PDF Creation of ${ documentHtmlFile} failed!\r :${ result. stderr} \r :Error code:${ result.rc} " )
185+ " PDF Creation of ${ documentHtmlFile} failed!\r :${ stderr} \r :Error code:${ result.rc} " )
183186 }
184-
185- fixDestinations(documentPDFFile. toFile())
186-
187- return Files . readAllBytes(documentPDFFile)
188- } catch (Throwable e) {
189- throw e
190- } finally {
191- if (documentPDFFile) {
192- Files . delete(documentPDFFile)
187+ }finally {
188+ if (result!= null ){
189+ result. stderr. close()
193190 }
194191 }
192+
193+
194+ File documentPDFFile = documentPDFFilePath. toFile()
195+ fixDestinations(documentPDFFile)
196+
197+ return documentPDFFile
195198 }
196199
197200 // Execute a command in the shell
198201 static private def Map shell (List<String > cmd ) {
202+
199203 def proc = cmd. execute()
200- // for some VERY complex docs - the process implementation hangs on wait() ...
201- // switching to the below - seem to work but gets a weird NPE...
202- // java.lang.NullPointerException: Cannot invoke method call() on null object
203- // at app.DocGen$Util.shell(DocGen.groovy:193)
204- ByteArrayOutputStream bosOut = new ByteArrayOutputStream ()
205- ByteArrayOutputStream bosErr = new ByteArrayOutputStream ()
206- try
204+ Path tempFilePath = Files . createTempFile(" shell" , " .bin" )
205+ File tempFile = tempFilePath. toFile()
206+ FileOutputStream tempFileOutputStream = new FileOutputStream (tempFile)
207+ def errOutputStream = new TeeOutputStream (tempFileOutputStream, System . err)
208+
209+ try
207210 {
208- proc. waitForProcessOutput(bosOut, bosErr )
209- } catch ( NullPointerException wtfEx) {
210- //
211+ proc. waitForProcessOutput(System . out, errOutputStream )
212+ }finally {
213+ tempFileOutputStream . close()
211214 }
212215
213216 return [
214217 rc : proc. exitValue(),
215- stderr : bosErr. toString(),
216- stdout : bosOut. toString()
218+ stderr : Files . newInputStream(tempFilePath, StandardOpenOption . DELETE_ON_CLOSE )
217219 ]
218220 }
219221
222+
223+
220224 /**
221225 * Fixes malformed PDF documents which use page numbers in local destinations, referencing the same document.
222226 * Page numbers should be used only for references to external documents.
@@ -232,6 +236,7 @@ ${data.metadata.header[1]}"""])
232236 def doc = PDDocument . load(file)
233237 fixDestinations(doc)
234238 doc. save(file)
239+ doc. close()
235240 }
236241
237242 /**
0 commit comments