我如何生成和下载PDF从markdown使用流星



我有一个文档的Meteor集合,其内容使用markdown样式(关于如何做到这一点的示例,请参阅:如何实际使用markdown与Meteor)

我的包栈如下:

npm

  • markdown-pdf
  • sanitize-filename
大气

  • coffeescript
  • iron-router

在查看文档时,我想向用户展示一个Download as PDF按钮。单击此按钮应将文档的标记转换为PDF文件并通过浏览器下载。

转换过程是受限的;它应该在服务器上运行,但不允许在服务器上保存任何临时文件。

我怎样才能做到这一点?

使用iron-router,创建服务器端路由来提供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

上述路由接收_id作为路由参数。这用于从Documents集合中检索相关的document。然后通过对document.title进行消毒,用连字符替换所有空格,生成PDF filename

响应头现在被设置为强制浏览器下载PDF作为带有经过处理的filename的文件。

PDF是使用markdown-pdf包从document.content markdown生成的。这个过程由于两个问题而变得复杂:

  1. 生成PDF的调用本质上是异步的,因此需要回调。这需要通过将其包装在Meteor的Async.runSynch方法中来转换为同步调用。这将返回一个我们可以使用的result属性的对象。

  2. markdown-pdf包有一个to.buffer方法,它返回一个包含生成PDF的buffer。这允许我们将所有内容保存在代码中,并且不需要将临时文件保存到服务器。为了将buffer管道到response,我们需要将其转换为流。我使用一个辅助BufferStream对象来为我做这件事(见下文)

有了这个路由,我只需要在我的显示模板的某个地方放置一个"下载为PDF"按钮(下面的代码是一个Jade链接样式为Bootstrap 3类按钮)

a.btn.btn-primary(href='{{pathFor "generatePDF"}}' target='_blank') Download as PDF 

最后,这里是BufferStream助手类:

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

最新更新