|
|
@ -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 |
|
|
|