UNPKG

4.59 kBJavaScriptView Raw
1var fs = require('fs')
2var childprocess = require('child_process')
3var path = require('path')
4var assert = require('assert')
5
6try {
7 var phantomjs = require('phantomjs-prebuilt')
8} catch (err) {
9 console.log('html-pdf: Failed to load PhantomJS module.', err)
10}
11
12/*
13* phantomjs version 1.8.1 and later should work.
14*
15* Create a PDF file out of an html string.
16*
17* Regions for the PDF page are:
18*
19* - Page Header -> document.getElementById('pageHeader')
20* - Page Content -> document.getElementById('pageContent')
21* - Page Footer -> document.getElementById('pageFooter')
22*
23* When no #pageContent is available, phantomjs will use document.body as pdf content
24*/
25module.exports = PDF
26function PDF (html, options) {
27 this.html = html
28 this.options = options || {}
29 if (this.options.script) {
30 this.script = path.normalize(this.options.script)
31 } else {
32 this.script = path.join(__dirname, 'scripts', 'pdf_a4_portrait.js')
33 }
34
35 if (this.options.filename) this.options.filename = path.resolve(this.options.filename)
36 if (!this.options.phantomPath) this.options.phantomPath = phantomjs && phantomjs.path
37 this.options.phantomArgs = this.options.phantomArgs || []
38 assert(this.options.phantomPath, "html-pdf: Failed to load PhantomJS module. You have to set the path to the PhantomJS binary using 'options.phantomPath'")
39 assert(typeof this.html === 'string' && this.html.length, "html-pdf: Can't create a pdf without an html string")
40 this.options.timeout = parseInt(this.options.timeout, 10) || 30000
41}
42
43PDF.prototype.toBuffer = function PdfToBuffer (callback) {
44 this.exec(function execPdfToBuffer (err, res) {
45 if (err) return callback(err)
46 fs.readFile(res.filename, function readCallback (err, buffer) {
47 if (err) return callback(err)
48 fs.unlink(res.filename, function unlinkPdfFile (err) {
49 if (err) return callback(err)
50 callback(null, buffer)
51 })
52 })
53 })
54}
55
56PDF.prototype.toStream = function PdfToStream (callback) {
57 this.exec(function (err, res) {
58 if (err) return callback(err)
59 try {
60 var stream = fs.createReadStream(res.filename)
61 } catch (err) {
62 return callback(err)
63 }
64
65 stream.on('end', function () {
66 fs.unlink(res.filename, function unlinkPdfFile (err) {
67 if (err) console.log('html-pdf:', err)
68 })
69 })
70
71 callback(null, stream)
72 })
73}
74
75PDF.prototype.toFile = function PdfToFile (filename, callback) {
76 assert(arguments.length > 0, 'html-pdf: The method .toFile([filename, ]callback) requires a callback.')
77 if (filename instanceof Function) {
78 callback = filename
79 filename = undefined
80 } else {
81 this.options.filename = path.resolve(filename)
82 }
83 this.exec(callback)
84}
85
86PDF.prototype.exec = function PdfExec (callback) {
87 var child = childprocess.spawn(this.options.phantomPath, [].concat(this.options.phantomArgs, [this.script]), this.options.childProcessOptions)
88 var stderr = []
89
90 var timeout = setTimeout(function execTimeout () {
91 respond(null, new Error('html-pdf: PDF generation timeout. Phantom.js script did not exit.'))
92 }, this.options.timeout)
93
94 function onError (buffer) {
95 stderr.push(buffer)
96 }
97
98 function onData (buffer) {
99 var result
100 try {
101 var json = buffer.toString().trim()
102 if (json) result = JSON.parse(json)
103 } catch (err) {
104 // Proxy for debugging purposes
105 process.stdout.write(buffer)
106 }
107
108 if (result) respond(null, null, result)
109 }
110
111 var callbacked = false
112 function respond (code, err, data) {
113 if (callbacked) return
114 callbacked = true
115 clearTimeout(timeout)
116
117 // If we don't have an exit code, we kill the process, ignore stderr after this point
118 if (code === null) kill(child, onData, onError)
119
120 if (!data) {
121 if (!err && code) err = new Error("html-pdf: Received the exit code '" + code + "'")
122 else if (!err) err = new Error('html-pdf: Unknown Error')
123
124 var postfix = stderr.length ? '\n' + Buffer.concat(stderr).toString() : ''
125 if (postfix) err.message += postfix
126 return callback(err, null)
127 }
128
129 callback(null, data)
130 }
131
132 child.stdout.on('data', onData)
133 child.stderr.on('data', onError)
134 child.on('error', function onError (err) { respond(null, err) })
135
136 // An exit event is most likely an error because we didn't get any data at this point
137 child.on('close', respond)
138 child.on('exit', respond)
139
140 var config = JSON.stringify({html: this.html, options: this.options})
141 child.stdin.write(config + '\n', 'utf8')
142 child.stdin.end()
143}
144
145function kill (child, onData, onError) {
146 child.stdin.end()
147 child.kill()
148}