Статьи

Генерация PDF в Rails

PDF

Возможность загрузки данных в формате PDF является распространенным требованием, с которым вы столкнетесь при создании веб-приложений. Есть разные способы, которыми это может быть достигнуто в Rails. Мы рассмотрим два основных способа генерации pdf-документов: с Ruby, использующим DSL для определения и оформления документов, или с помощью библиотеки, которая преобразует ваш HTML в PDF.

Сегодня мы сосредоточимся на трех популярных жемчужинах:

  • Креветка (которая использует метод DSL)
  • PDFKit (который использует генератор)
  • Злой PDF (также использует генератор).

HTML в PDF или Ruby поколения?

Ответ на это обычно зависит от предпочтений и требований проекта. HTML в PDF может быть быстрее, особенно если у вас уже есть представление, отображающее содержимое, которое вы хотите в вашем PDF. В этом случае вам не нужно будет писать намного больше кода для создания PDF-файла. Однако этот метод может усложнить управление макетом документа, особенно при работе с многостраничными документами. Контент будет обрезан и разделен между страницами. Это правда, что с некоторыми стилями CSS вы можете контролировать разрывы страниц. Однако для более сложных документов PDF, которые занимают несколько страниц и содержат содержимое переменной длины, верхние и нижние колонтитулы, будет трудно контролировать способ отображения каждой страницы. В этих случаях может иметь смысл использовать креветки.

Используя такую ​​библиотеку, как Prawn, вы должны самостоятельно выполнять стилизацию и позиционирование контента, используя DSL Prawn. Преимущество здесь — больше контроля над тем, как все отображается и где страницы ломаются.

Мы собираемся создать файл PDF для веб-страницы, показанной ниже, который содержит статический текст, изображение и таблицу некоторых записей базы данных.

pdf_webpage_image

Прон

Чтобы использовать Prawn, включите гем в ваш Gemfile и запустите пакетную bundle install

 gem 'prawn' 

Зарегистрируйте PDF-файл config/initializers/mime_types.rb файле 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 без содержимого, когда .pdf добавляется в конец определенного URL-адреса. В моем случае 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 см. В руководстве по креветкам.

PDFKit

Для PDFKit сначала включите гем в свой Gemfile

 gem 'pdfkit' 

и запустите bundle install

Вы можете создавать PDF-документы, указывая на HTML-файл или веб-сайт, как показано ниже.

 # 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-файлы любой страницы на веб-сайте, добавляя .pdf в конец URL-адреса. Это то, что мы собираемся использовать здесь.

Чтобы добавить промежуточное программное обеспечение, /config/application.rb файл /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, добавьте к тегу идентификатор или имя класса и установите для него свойство отображения в 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 найти на этой вики-странице .

Другой вариант установки wkhtmltopdf файлов wkhtmltopdf — через гем wkhtmltopdf-binary . Добавьте его в свой Gemfile и запустите пакетную bundle install

 gem 'wkhtmltopdf-binary' 

II

Если содержимое обрезается там, где вы этого не хотите, например, таблица разбивается на две страницы, вы можете указать разрыв страницы перед ее отображением, чтобы она отображалась на отдельной странице.

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

III

wkhtmltopdf плохо работает с относительными URL-адресами любых внешних файлов (изображений, таблиц стилей, JavaScript), которые вы можете использовать. Если вы используете обычные теги, такие как stylesheet_link_tag и пытаетесь сгенерировать документ, wkhtmltopdf будет зависать при загрузке ресурсов. Использование абсолютных путей (файловых путей или URL-адресов, включая домен) для ресурсов решает эту проблему.

Другое возможное решение — использовать встроенные стили. Например, вместо тега stylesheet_link_tag вы можете использовать:

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

IV

Для любого содержимого, которое появляется на веб-странице, но которое вы не хотите отображать в документе 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' 

Зарегистрируйте mime-тип PDF в config/initializers/mime_types.rb

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

Как и PDFKit, Wicked PDF поставляется с промежуточным программным обеспечением, которое позволяет пользователям просматривать PDF-страницы любой страницы вашего сайта, добавляя .pdf к URL-адресу. Это достигается добавлением config.middleware.use WickedPdf::Middleware в файл /config/application.rb . Я не буду использовать промежуточное программное обеспечение здесь. Вместо этого я создам файлы шаблонов и макетов для PDF и изменим действие моего контроллера для обработки запросов в формате PDF.

Я изменил ProductsController как показано на рисунке. Здесь я указываю имя файла PDF и макет, используемый для его создания. Для получения дополнительной информации о доступных опциях, которые вы можете использовать, посмотрите файл README .

 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, поэтому любой файл, на который вы wkhtmltopdf должен быть включен с абсолютным адресом.

Использование обычных тегов stylesheet_link_tag и javascript_include_tag приведет к зависанию приложения при запросе PDF. Кроме того, изображение не будет отображаться, если используется обычный тег img .

Вывод

Мы рассмотрели три различных подхода к генерации PDF. Решение о том, какой вариант использовать, зависит от множества факторов, включая предпочитаемый вами язык (HTML или DSL), простоту выполнения задачи с одной библиотекой над другой, сложность формата документа PDF и другие соображения. Я надеюсь, что эта статья поможет вам принять это решение.