Merge pull request #7982 from opf/feature/formatted_text_in_pdf_export

print formatted description and comments in pdf export
pull/7985/head
ulferts 5 years ago committed by GitHub
commit 7641247ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Gemfile
  2. 5
      Gemfile.lock
  3. 1
      app/models/work_package/pdf_export/attachments.rb
  4. 133
      app/models/work_package/pdf_export/common.rb
  5. 83
      app/models/work_package/pdf_export/work_package_list_to_pdf.rb
  6. 38
      app/models/work_package/pdf_export/work_package_to_pdf.rb

@ -137,7 +137,7 @@ gem 'lograge', '~> 0.10.0'
gem 'airbrake', '~> 9.4.3', require: false gem 'airbrake', '~> 9.4.3', require: false
gem 'prawn', '~> 2.2' gem 'prawn', '~> 2.2'
gem 'prawn-table', '~> 0.2.2' gem 'prawn-markup', '~> 0.2.1'
gem 'cells-erb', '~> 0.1.0' gem 'cells-erb', '~> 0.1.0'
gem 'cells-rails', '~> 0.0.9' gem 'cells-rails', '~> 0.0.9'

@ -696,6 +696,10 @@ GEM
prawn (2.2.2) prawn (2.2.2)
pdf-core (~> 0.7.0) pdf-core (~> 0.7.0)
ttfunk (~> 1.5) ttfunk (~> 1.5)
prawn-markup (0.2.1)
nokogiri
prawn
prawn-table
prawn-table (0.2.2) prawn-table (0.2.2)
prawn (>= 1.3.0, < 3.0.0) prawn (>= 1.3.0, < 3.0.0)
pry (0.12.2) pry (0.12.2)
@ -1062,6 +1066,7 @@ DEPENDENCIES
plaintext (~> 0.3.2) plaintext (~> 0.3.2)
posix-spawn (~> 0.3.13) posix-spawn (~> 0.3.13)
prawn (~> 2.2) prawn (~> 2.2)
prawn-markup (~> 0.2.1)
prawn-table (~> 0.2.2) prawn-table (~> 0.2.2)
pry-byebug (~> 3.7.0) pry-byebug (~> 3.7.0)
pry-rails (~> 0.3.6) pry-rails (~> 0.3.6)

@ -29,7 +29,6 @@
#++ #++
module WorkPackage::PdfExport::Attachments module WorkPackage::PdfExport::Attachments
## ##
# Creates cells for each attachment of the work package # Creates cells for each attachment of the work package
# #

@ -34,6 +34,9 @@ module WorkPackage::PdfExport::Common
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
include CustomFieldsHelper include CustomFieldsHelper
include WorkPackage::PdfExport::ToPdfHelper include WorkPackage::PdfExport::ToPdfHelper
include OpenProject::TextFormatting
private
def field_value(work_package, attribute) def field_value(work_package, attribute)
value = work_package.send(attribute) value = work_package.send(attribute)
@ -64,71 +67,113 @@ module WorkPackage::PdfExport::Common
end end
## ##
# Creates a number of cell rows to show the description. # Writes the formatted work package description into the document.
#
# The description is split into many smaller cells so that
# prawn-table does not go crazy with long texts causing
# empty pages in between.
# #
# The fact that prawn-table can't handle multi-page table cells # A border (without one on the top) is painted around the area painted by the description.
# is a known, unsolved issue. Hence this workaround.
# #
# @param description [String] The work package's description # @param work_package [WorkPackage] The work package for which the description is to be printed.
# @param options [Hash] Allows changing the number of lines per cell # @param label [boolean] Whether a label is to be printed in a column preceding the description.
# through the :max_lines_per_cell option. def write_description!(work_package, label = true)
# @return [Array] An array of rows to be added to the work packages table. height = write_description_html!(work_package, label)
def make_description(description, options = {})
lines = description.lines data = make_description_label_row(label) +
max = options[:max_lines_per_cell] || max_lines_per_description_cell make_description_border_rows(height, label)
if lines.size > max_lines_per_description_cell pdf.table(data, column_widths: column_widths)
cells = split_description lines, max: max, cell_options: Hash(options[:cell_options]) end
format_description_segments!(cells) def write_description_html!(work_package, label)
else float_with_height_indicator do
[make_single_description(description, Hash(options[:cell_options]))] pdf.move_down(cell_padding[1])
pdf.indent(description_padding_left(label), cell_padding[3]) do
pdf.markup(formatted_description_text(work_package))
end
end end
end end
## def float_with_height_indicator
# Formats the cells so that they appear to be one big cell. former_position = new_position = current_y_position
def format_description_segments!(cells)
cells.first.padding[0] = cell_padding[0] # top padding pdf.float do
cells.last.padding[2] = cell_padding[2] # bottom padding yield
cells.last.borders = [:left, :right, :bottom]
cells new_position = current_y_position
end
position_diff(former_position, new_position)
end end
def split_description(lines, max: max_lines_per_description_cell, cell_options: {}) def make_description_label_row(label)
head = make_description_segment lines.take(max).join, cell_options if label
[[make_description_label, pdf.make_cell('', borders: [:right], colspan: description_colspan)].compact]
else
[[pdf.make_cell('', borders: %i[right left], colspan: description_colspan)]]
end
end
if lines.size > max def description_padding_left(label)
[head] + split_description(lines.drop(max), max: max, cell_options: cell_options) if label
column_widths.first + cell_padding[1]
else else
[head] cell_padding[1]
end end
end end
def make_description_segment(description, options = {}) def description_padding_right
cell_options = { cell_padding[3]
borders: [:left, :right], end
padding: [0, cell_padding[1], 0, cell_padding[3]]
}
make_single_description description, cell_options.merge(options) def formatted_description_text(work_package)
format_text(work_package.description.to_s, object: work_package, format: :html)
end end
def make_single_description(description, options = {}) def make_description_border_rows(height, label)
cell_options = { colspan: description_colspan } border_row_height = 10
pdf.make_cell description, cell_options.merge(options) (height / border_row_height).ceil.times.map do |index|
make_description_border_row(border_row_height,
label,
index == (height / border_row_height).ceil - 1)
end
end end
def max_lines_per_description_cell def make_description_border_row(border_row_height, label, last)
3 border_left = if label
[:left]
else
%i[left right]
end
border_right = [:right]
if last
border_left << :bottom
border_right << :bottom
end
if label
[pdf.make_cell('', height: border_row_height, borders: border_left, colspan: 1),
pdf.make_cell('', height: border_row_height, borders: border_right, colspan: description_colspan)]
else
[pdf.make_cell('', height: border_row_height, borders: border_left, colspan: description_colspan)]
end
end
def make_description_label
text = WorkPackage.human_attribute_name(:description) + ':'
pdf.make_cell(text, borders: [:left], font_style: :bold, padding: cell_padding)
end end
def description_colspan def description_colspan
raise NotImplementedError, 'to be implemented where included' raise NotImplementedError, 'to be implemented where included'
end end
def current_y_position
OpenStruct.new y: pdf.y, page: pdf.page_number
end
def position_diff(position_a, position_b)
position_a.y - position_b.y + (position_b.page - position_a.page) * pdf.bounds.height
end
end end

@ -47,10 +47,7 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
end end
def render! def render!
write_title! write_content!
write_work_packages!
write_footers!
success(pdf.render) success(pdf.render)
rescue Prawn::Errors::CannotFit rescue Prawn::Errors::CannotFit
@ -60,6 +57,14 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
error(I18n.t(:error_pdf_failed_to_export, error: e.message)) error(I18n.t(:error_pdf_failed_to_export, error: e.message))
end end
def write_content!
write_title!
write_headers!
write_work_packages!
write_footers!
end
def project def project
query.project query.project
end end
@ -95,11 +100,6 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
style: :italic style: :italic
end end
def write_work_packages!
pdf.font style: :normal, size: 8
pdf.table(data, column_widths: column_widths)
end
def column_widths def column_widths
widths = valid_export_columns.map do |col| widths = valid_export_columns.map do |col|
if col.name == :subject || text_column?(col) if col.name == :subject || text_column?(col)
@ -122,8 +122,9 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
['string', 'text'].include?(column.custom_field.field_format) ['string', 'text'].include?(column.custom_field.field_format)
end end
def data def write_headers!
[data_headers] + data_rows pdf.font style: :normal, size: 8
pdf.table([data_headers], column_widths: column_widths)
end end
def data_headers def data_headers
@ -132,44 +133,52 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
end end
end end
def data_rows def write_work_packages!
pdf.font style: :normal, size: 8
previous_group = nil previous_group = nil
work_packages.flat_map do |work_package| work_packages.each do |work_package|
values = valid_export_columns.map do |column| previous_group = write_group_header!(work_package, previous_group)
make_column_value work_package, column
end
result = [values] write_attributes!(work_package)
if options[:show_descriptions] if options[:show_descriptions]
make_description(work_package.description.to_s).each do |segment| write_description!(work_package, false)
result << [segment]
end
end end
if options[:show_attachments] && work_package.attachments.exists? if options[:show_attachments] && work_package.attachments.exists?
attachments = make_attachments_cells(work_package.attachments) write_attachments!(work_package)
if attachments.any?
result << [
{ content: pdf.make_table([attachments]), colspan: description_colspan }
]
end
end end
end
end
if query.grouped? && (group = query.group_by_column.value(work_package)) != previous_group def write_attributes!(work_package)
label = make_group_label(group) values = valid_export_columns.map do |column|
previous_group = group make_column_value work_package, column
end
result.insert 0, [ pdf.table([values], column_widths: column_widths)
pdf.make_cell(label, font_style: :bold, end
colspan: valid_export_columns.size,
background_color: 'DDDDDD') def write_attachments!(work_package)
] attachments = make_attachments_cells(work_package.attachments)
end
pdf.table([attachments], width: pdf.bounds.width) if attachments.any?
end
result def write_group_header!(work_package, previous_group)
if query.grouped? && (group = query.group_by_column.value(work_package)) != previous_group
label = make_group_label(group)
group_cell = pdf.make_cell(label,
font_style: :bold,
colspan: valid_export_columns.size,
background_color: 'DDDDDD')
pdf.table([[group_cell]], column_widths: column_widths)
group
else
previous_group
end end
end end

@ -173,42 +173,10 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
make_custom_fields.each { |row| data << row } make_custom_fields.each { |row| data << row }
options = {
cell_options: { borders: [:right] }
}
segments = make_description(work_package.description.to_s, options)
segments.each_with_index do |seg, index|
data << make_description_row(seg,
first: index == 0,
last: index == segments.size - 1)
end
pdf.font style: :normal, size: 9 pdf.font style: :normal, size: 9
pdf.table(data, column_widths: column_widths) pdf.table(data, column_widths: column_widths)
end
def make_description_row(seg, first: false, last: false)
if first
label = make_description_label
else
label = make_empty_label
end
if last
label.borders = [:left, :bottom]
seg.borders = [:bottom, :right]
end
[label, seg] write_description!(work_package)
end
def make_description_label
text = WorkPackage.human_attribute_name(:description) + ':'
pdf.make_cell(text, borders: [:left], font_style: :bold, padding: cell_padding)
end
def make_empty_label
pdf.make_cell '', borders: [:left]
end end
def write_changesets! def write_changesets!
@ -260,6 +228,7 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
text = journal text = journal
.render_detail(detail, no_html: true, only_path: false) .render_detail(detail, no_html: true, only_path: false)
.gsub(/\((https?[^\)]+)\)$/, "(<link href='\\1'>\\1</link>)") .gsub(/\((https?[^\)]+)\)$/, "(<link href='\\1'>\\1</link>)")
pdf.text('- ' + text, inline_format: true) pdf.text('- ' + text, inline_format: true)
newline! newline!
end end
@ -268,7 +237,8 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
newline! unless journal.details.empty? newline! unless journal.details.empty?
pdf.font style: :normal, size: 8 pdf.font style: :normal, size: 8
pdf.text journal.notes.to_s
pdf.markup(format_text(journal.notes.to_s, object: work_package, format: :html))
end end
newline! newline!

Loading…
Cancel
Save