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 'prawn', '~> 2.2'
gem 'prawn-table', '~> 0.2.2'
gem 'prawn-markup', '~> 0.2.1'
gem 'cells-erb', '~> 0.1.0'
gem 'cells-rails', '~> 0.0.9'

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

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

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

@ -47,10 +47,7 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
end
def render!
write_title!
write_work_packages!
write_footers!
write_content!
success(pdf.render)
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))
end
def write_content!
write_title!
write_headers!
write_work_packages!
write_footers!
end
def project
query.project
end
@ -95,11 +100,6 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
style: :italic
end
def write_work_packages!
pdf.font style: :normal, size: 8
pdf.table(data, column_widths: column_widths)
end
def column_widths
widths = valid_export_columns.map do |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)
end
def data
[data_headers] + data_rows
def write_headers!
pdf.font style: :normal, size: 8
pdf.table([data_headers], column_widths: column_widths)
end
def data_headers
@ -132,44 +133,52 @@ class WorkPackage::PdfExport::WorkPackageListToPdf < WorkPackage::Exporter::Base
end
end
def data_rows
def write_work_packages!
pdf.font style: :normal, size: 8
previous_group = nil
work_packages.flat_map do |work_package|
values = valid_export_columns.map do |column|
make_column_value work_package, column
end
work_packages.each do |work_package|
previous_group = write_group_header!(work_package, previous_group)
result = [values]
write_attributes!(work_package)
if options[:show_descriptions]
make_description(work_package.description.to_s).each do |segment|
result << [segment]
end
write_description!(work_package, false)
end
if options[:show_attachments] && work_package.attachments.exists?
attachments = make_attachments_cells(work_package.attachments)
if attachments.any?
result << [
{ content: pdf.make_table([attachments]), colspan: description_colspan }
]
end
write_attachments!(work_package)
end
end
end
if query.grouped? && (group = query.group_by_column.value(work_package)) != previous_group
label = make_group_label(group)
previous_group = group
def write_attributes!(work_package)
values = valid_export_columns.map do |column|
make_column_value work_package, column
end
result.insert 0, [
pdf.make_cell(label, font_style: :bold,
colspan: valid_export_columns.size,
background_color: 'DDDDDD')
]
end
pdf.table([values], column_widths: column_widths)
end
def write_attachments!(work_package)
attachments = make_attachments_cells(work_package.attachments)
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

@ -173,42 +173,10 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
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.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]
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]
write_description!(work_package)
end
def write_changesets!
@ -260,6 +228,7 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
text = journal
.render_detail(detail, no_html: true, only_path: false)
.gsub(/\((https?[^\)]+)\)$/, "(<link href='\\1'>\\1</link>)")
pdf.text('- ' + text, inline_format: true)
newline!
end
@ -268,7 +237,8 @@ class WorkPackage::PdfExport::WorkPackageToPdf < WorkPackage::Exporter::Base
newline! unless journal.details.empty?
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
newline!

Loading…
Cancel
Save