能够以 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'
在文件中注册 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-binary
gem 包含在 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
结论
总结所有的面试题目都不是一成不变的,特别是像一线大厂,上面的面试题只是给大家一个借鉴作用,最主要的是给自己增加知识的储备,有备无患。最后给大家分享Spring系列的学习笔记和面试题,包含spring面试题、spring cloud面试题、spring boot面试题、spring教程笔记、spring boot教程笔记、最新阿里巴巴开发手册(63页PDF总结)、2022年Java面试手册。一共整理了1184页PDF文档。私信博主(777)领取,祝大家更上一层楼!!!