Reputation: 19123
I have a Meteor collection of documents whose content is styled using markdown (for an example of how to do this please see: How does one actually use Markdown with Meteor)
My package stack is as follows:
npm
Atmosphere
When viewing a document I would like to present my users with a Download as PDF
button. Clicking on this should convert the document's markdown to a PDF file and download it via the browser.
The conversion process is constrained; it should run on the server but is not allowed to save any temporary files to the server.
How can I achieve this?
Upvotes: 0
Views: 1097
Reputation: 19123
Using iron-router
, create a server-side route to serve up the PDF.
Router.route '/api/pdf/:_id',
name : 'generatePDF'
where: 'server'
action: ->
document= Documents.findOne @params._id
sanitize = Meteor.npmRequire 'sanitize-filename'
filename = sanitize(document.title).replace(/\s+/g, '-')
@response.writeHead 200,
'Content-Type': 'application/pdf'
'Content-Disposition': "attachment; filename=#{filename}.pdf"
markdown = document.content
Async.runSync (done)->
Meteor.npmRequire('markdown-pdf')
.from.string(markdown)
.to.buffer (err, buffer)->
done null, new BufferStream buffer
.result.pipe @response
The route above receives an _id
as a route parameter. This is used to retrieve the associated document
from the Documents
collection. The PDF filename
is then generated by sanitizing the document.title
replacing all spaces with hyphens.
The response headers are now set to force the browser to download the PDF as a file with the sanitized filename
.
The PDF is generated from the document.content
markdown using the markdown-pdf
package. This process is complicated by two issues:
The call to generate the PDF is intrinsically asynchronous and therefore requires a callback. This needs to be converted to a synchronous call by wrapping it in Meteor's Async.runSynch
method. This returns an object with a result
property we can use.
The markdown-pdf
package has a to.buffer
method that returns a buffer
containing the generated PDF. This allows us to keep everything in code and removes any need to save a temporary file to the server. To pipe this buffer
into the response
we need to convert it to a stream. I use a helper BufferStream
object to do this for me (see below)
With this route in place I just need to place a 'Download as PDF' button somewhere on my display template (the code below is a Jade link styled as a button by Bootstrap 3 classes)
a.btn.btn-primary(href='{{pathFor "generatePDF"}}' target='_blank') Download as PDF
And finally, here is that BufferStream
helper class:
stream = Meteor.npmRequire "stream"
class @BufferStream extends stream.Readable
constructor: (@source, @offset = 0) ->
throw new Meteor.Error 'InvalidBuffer', 'BufferStream source must be a buffer.' unless Buffer.isBuffer(@source)
super
@length = @source.length
_read: (size) ->
if @offset < @length
@push @source.slice @offset, @offset + size
@offset += size
@push null if @offset >= @length
Upvotes: 1