8年老程序员帮我们已经在 Rails 中生成 PDF

能够以 pdf 格式下载数据是您在构建 Web 应用程序时会遇到的常见要求。在 Rails 中有不同的方法可以实现这一点。我们将研究用于生成 pdf 文档的两种主要方法:使用 Ruby 使用 DSL 定义和样式化文档,或者使用将 HTML 转换为 PDF 的库。

今天我们将重点介绍三种流行的宝石:

大虾(使用 DSL 方法)

PDFKit(使用生成器)

邪恶的 PDF(也使用生成器)。

HTML 到 PDF 或 Ruby 生成?

这个问题的答案通常取决于偏好和项目要求。HTML 到 PDF 可以更快,特别是如果您已经有一个视图来显示您想要在 PDF 中显示的内容。在这种情况下,您不必编写更多代码来生成 PDF 文件。但是,这种方法会使控制文档的布局变得更加困难,尤其是在处理多页文档时。内容往往会被截断并在页面之间拆分。确实,通过一些 CSS 样式,您可以对分页符进行一些控制。但是,对于跨越多个页面并包含可变长度内容、页眉和页脚的更复杂的 PDF 文档,将难以控制每个页面的呈现方式。在这些情况下,使用 Prawn 可能更有意义。

使用像 Prawn 这样的库,您必须使用 Prawn 的 DSL 自己完成所有内容样式和定位。这里的优点是可以更好地控制事物的显示方式和页面中断的位置。

我们将为如下所示的网页生成一个 PDF 文件,其中包含一些静态文本、一张图像和一些数据库记录的表格。

要使用 Prawn,请将 gem 包含在 Gemfile 中并运行bundle install

gem 'prawn' 

'prawn'在文件中注册 PDF mime 类型
config/initializers/mime_types.rb。

Mime::Type.register "application/pdf", :pdf

现在我们需要设置控制器动作来响应对 PDF 格式的请求。

对于我的 Products 控制器,我有一个index要修改的操作,如图所示。

class ProductsController < ApplicationController
  def index
    @products = Product.all

    respond_to do |format|
      format.html
      format.pdf do
        pdf = Prawn::Document.new
        send_data pdf.render, filename: 'report.pdf', type: 'application/pdf'
      end
    end

  end
end 

当.pdf附加到特定 url 的末尾时,上面将生成一个没有内容的 PDF 文件。就我而言
http://localhost:3000/products.pdf。

为了从控制器中分离出 pdf 生成代码,我创建了一个app/pdfs目录并在文件中添加了一个新类app/pdfs/report_pdf.rb。

我更改了控制器代码以使用新类。

class ProductsController < ApplicationController
  def index
    @products = Product.all

    respond_to do |format|
      format.html
      format.pdf do
        pdf = ReportPdf.new(@products)
        send_data pdf.render, filename: 'report.pdf', type: 'application/pdf'
      end
    end

  end
end 

下面的代码显示了如何生成上面显示的网页的 PDF。我已经评论它以显示我在做什么。

class ReportPdf < Prawn::Document
  def initialize(products)
    super()
    @products = products
    header
    text_content
    table_content
  end

  def header
    #This inserts an image in the pdf file and sets the size of the image
    image "#{Rails.root}/app/assets/images/header.png", width: 530, height: 150
  end

  def text_content
    # The cursor for inserting content starts on the top left of the page. Here we move it down a little to create more space between the text and the image inserted above
    y_position = cursor - 50

    # The bounding_box takes the x and y coordinates for positioning its content and some options to style it
    bounding_box([0, y_position], :width => 270, :height => 300) do
      text "Lorem ipsum", size: 15, style: :bold
      text "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse interdum semper placerat. Aenean mattis fringilla risus ut fermentum. Fusce posuere dictum venenatis. Aliquam id tincidunt ante, eu pretium eros. Sed eget risus a nisl aliquet scelerisque sit amet id nisi. Praesent porta molestie ipsum, ac commodo erat hendrerit nec. Nullam interdum ipsum a quam euismod, at consequat libero bibendum. Nam at nulla fermentum, congue lectus ut, pulvinar nisl. Curabitur consectetur quis libero id laoreet. Fusce dictum metus et orci pretium, vel imperdiet est viverra. Morbi vitae libero in tortor mattis commodo. Ut sodales libero erat, at gravida enim rhoncus ut."
    end

    bounding_box([300, y_position], :width => 270, :height => 300) do
      text "Duis vel", size: 15, style: :bold
      text "Duis vel tortor elementum, ultrices tortor vel, accumsan dui. Nullam in dolor rutrum, gravida turpis eu, vestibulum lectus. Pellentesque aliquet dignissim justo ut fringilla. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut venenatis massa non eros venenatis aliquet. Suspendisse potenti. Mauris sed tincidunt mauris, et vulputate risus. Aliquam eget nibh at erat dignissim aliquam non et risus. Fusce mattis neque id diam pulvinar, fermentum luctus enim porttitor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."
    end

  end

  def table_content
    # This makes a call to product_rows and gets back an array of data that will populate the columns and rows of a table
    # I then included some styling to include a header and make its text bold. I made the row background colors alternate between grey and white
    # Then I set the table column widths
    table product_rows do
      row(0).font_style = :bold
      self.header = true
      self.row_colors = ['DDDDDD', 'FFFFFF']
      self.column_widths = [40, 300, 200]
    end
  end

  def product_rows
    [['#', 'Name', 'Price']] +
      @products.map do |product|
      [product.id, product.name, product.price]
    end
  end
end 

有关可用的 PDF 格式规则的更多信息,请查看 Prawn手册。

PDFKit

对于 PDFKit,首先在 Gemfile 中包含 gem

gem 'pdfkit' 

并运行bundle install

您可以通过指向 html 文件或网站来生成 PDF 文档,如下所示。

# From https://github.com/pdfkit/pdfkit#usage

# PDFKit.new takes the HTML and any options for wkhtmltopdf
# run `wkhtmltopdf --extended-help` for a full list of options
kit = PDFKit.new(html, :page_size => 'Letter')
kit.stylesheets << '/path/to/css/file'

# Get an inline PDF
pdf = kit.to_pdf

# Save the PDF to a file
file = kit.to_file('/path/to/save/pdf')

# PDFKit.new can optionally accept a URL or a File.
# Stylesheets can not be added when source is provided as a URL of File.
kit = PDFKit.new('http://google.com')
kit = PDFKit.new(File.new('/path/to/html'))

# Add any kind of option through meta tags
PDFKit.new('<html><head><meta name="pdfkit-page_size" content="Letter"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name1" content="cookie_value1"')
PDFKit.new('<html><head><meta name="pdfkit-cookie cookie_name2" content="cookie_value2"') 

您还可以使用中间件解决方案,允许用户通过附加.pdf到 URL 的末尾来生成网站上任何页面的 PDF。这就是我们将在这里使用的。

要添加中间件,请在文件中包含以下内容/config/application.rb(适用于 Rails 版本 3 及更高版本)。

module RailsPdf
  class Application < Rails::Application
      config.middleware.use PDFKit::Middleware
      .
      .
      .
  end
end 

重新启动服务器,导航到一个页面,然后添加.pdf到 URL 的末尾。您将获得该网页的 PDF 版本。

也可以使用链接将页面下载为 PDF 文件。:

= link_to 'Download Report', products_path(format: 'pdf') 

要排除 pdf 文件中的链接,请在标签中添加 id 或类名,并在 CSS 中为其设置显示属性。

@media print {
  .pdf_exclude {
        display: none;
  }
}

一些注意事项

一世

如果wkhtmltopdf您的系统上没有安装,那么您将收到类似的错误,如下所示

PDFKit::NoExecutableError in ProductsController#index

No wkhtmltopdf executable found at >> Please install wkhtmltopdf - https://github.com/pdfkit/PDFKit/wiki/Installing-WKHTMLTOPDF 

有关如何安装的说明,请wkhtmltopdf查看此wiki 页面。

安装二进制文件的另一个选项wkhtmltopdf是通过 gem wkhtmltopdf-binary。将它添加到您的 Gemfile 并运行bundle install

gem 'wkhtmltopdf-binary' 

伊尔

如果内容在您不希望的地方被截断,例如表格被分成两页,您可以在表格呈现之前指定分页符,以便它出现在自己的页面上。

@media print {
  .page-break {
    display: block;
    page-break-before: always;
  }
} 

wkhtmltopdf不能很好地处理您可能正在使用的任何外部文件(图像、样式表、javascript)的相对 URL。如果您使用常规标签之类的stylesheet_link_tag并尝试生成文档,wkhtmltopdf则在加载资产时会挂起。使用资产的绝对路径(文件路径或包含域的 url)可以解决这个问题。

另一种可能的解决方案是使用内联样式。例如,stylesheet_link_tag您可以使用以下标签代替标签:

<style type="text/css">
    <%= Rails.application.assets.find_asset('application.css').to_s.html_safe %>
</style> 

对于出现在网页上但您不希望在 pdf 文档中显示的任何内容(例如上例中的“下载 PDF”链接),只需将其标记并使用 CSS 隐藏即可。

@media print {
    .hide_in_pdf {
        display:none;
    }
} 

邪恶的PDF

要使用 Wicked PDF,首先安装wkhtmltopdf。或者,您可以通过将wkhtmltopdf-binarygem 包含在 Gemfile 中来使用它。

添加wicked_pdf到您的 Gemfile 并运行bundle install.

gem 'wicked_pdf' 

在中注册 PDF mime 类型
config/initializers/mime_types.rb

Mime::Type.register "application/pdf", :pdf 

与 PDFKit 一样,Wicked PDF 带有一个中间件,允许用户通过附加.pdf到 URL 来获取您网站上任何页面的 PDF 视图。这是通过添加config.middleware.use WickedPdf::Middleware到/config/application.rb文件来实现的。我不会在这里使用中间件。相反,我将为 PDF 创建模板和布局文件,并修改我的控制器操作以处理 PDF 格式的请求。

我修改了ProductsController如图所示。在这里,我指定了 PDF 文件的名称和用于生成它的布局。有关您可以使用的可用选项的更多信息,请查看自述文件。

class ProductsController < ApplicationController
  def index
    @products = Product.all
    respond_to do |format|
      format.html
      format.pdf do
        render :pdf => "report", :layout => 'pdf.html.haml'
      end
    end
  end
end 

这是布局文件
app/views/layouts/pdf.html.haml。

!!!
%html
  %head
  %title RailsWickedPdf
    = wicked_pdf_stylesheet_link_tag    "application", :media => "all"
    = wicked_pdf_javascript_include_tag "application"
    = csrf_meta_tags
  %body
    = yield 

和模板文件
app/views/products/index.pdf.haml。

.container
  .row
    = wicked_pdf_image_tag('header.png')
  .row
    .col-xs-6
      %h3
        Lorem ipsum
      %p
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse interdum semper placerat. Aenean mattis fringilla risus ut fermentum. Fusce posuere dictum venenatis. Aliquam id tincidunt ante, eu pretium eros. Sed eget risus a nisl aliquet scelerisque sit amet id nisi. Praesent porta molestie ipsum, ac commodo erat hendrerit nec. Nullam interdum ipsum a quam euismod, at consequat libero bibendum. Nam at nulla fermentum, congue lectus ut, pulvinar nisl. Curabitur consectetur quis libero id laoreet. Fusce dictum metus et orci pretium, vel imperdiet est viverra. Morbi vitae libero in tortor mattis commodo. Ut sodales libero erat, at gravida enim rhoncus ut.
    .col-xs-6
      %h3
        Duis vel
      %p
        Duis vel tortor elementum, ultrices tortor vel, accumsan dui. Nullam in dolor rutrum, gravida turpis eu, vestibulum lectus. Pellentesque aliquet dignissim justo ut fringilla. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut venenatis massa non eros venenatis aliquet. Suspendisse potenti. Mauris sed tincidunt mauris, et vulputate risus. Aliquam eget nibh at erat dignissim aliquam non et risus. Fusce mattis neque id diam pulvinar, fermentum luctus enim porttitor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
  .row
    %table.table.table-striped
      %thead
        %th #
        %th Product
        %th Price
      %tbody
        - @products.each do |product|
          %tr
            %td
              = product.id
            %td
              = product.name
            %td
              = product.price 

您会注意到在上面的两个文件中,我使用了以下 Wicked 助手。

wicked_pdf_stylesheet_link_tag
wicked_pdf_javascript_include_tag
wicked_pdf_image_tag 

如果您使用外部文件,这些是必需的。wkhtmltopdf在 Rails 应用程序之外运行,因此您链接到的任何文件都必须包含绝对地址。

当请求 PDF 时,使用常规stylesheet_link_tag和标签将导致应用程序挂起。此外,如果使用常规标签javascript_include_tag,则不会呈现图像。img

结论

我们已经研究了三种不同的 PDF 生成方法。决定使用哪个取决于多种因素,包括您的首选语言(HTML 或 DSL)、使用一个库而不是另一个库完成任务的难易程度、PDF 文档格式的复杂性以及其他考虑因素。我希望这篇文章能帮助你做出这个决定,需要更多的学习资料+关注博主私信博主免费获取更多的学习资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值