From 9a78fed7b1020fded9dc79c5259dcc4aa56eed60 Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 7 Aug 2023 17:22:12 +0200 Subject: [PATCH] [#48857] Cover page for pdf reports https://community.openproject.org/work_packages/48857 --- app/assets/images/pdf/cover.png | Bin 0 -> 45547 bytes app/models/work_package/pdf_export/common.rb | 40 +++++- app/models/work_package/pdf_export/cover.rb | 136 ++++++++++++++++++ app/models/work_package/pdf_export/page.rb | 23 ++- app/models/work_package/pdf_export/style.rb | 64 +++++++++ .../pdf_export/work_package_list_to_pdf.rb | 22 +-- 6 files changed, 266 insertions(+), 19 deletions(-) create mode 100644 app/assets/images/pdf/cover.png create mode 100644 app/models/work_package/pdf_export/cover.rb diff --git a/app/assets/images/pdf/cover.png b/app/assets/images/pdf/cover.png new file mode 100644 index 0000000000000000000000000000000000000000..4851c6260239982a8f3dc784cff1467ef59d8f43 GIT binary patch literal 45547 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 Q0|Q@1r%m(=b#IdYAB*!u9{>OV literal 0 HcmV?d00001 diff --git a/app/models/work_package/pdf_export/common.rb b/app/models/work_package/pdf_export/common.rb index 81bbdd84542..41a37e03dc5 100644 --- a/app/models/work_package/pdf_export/common.rb +++ b/app/models/work_package/pdf_export/common.rb @@ -91,7 +91,7 @@ module WorkPackage::PDFExport::Common def get_column_value_cell(work_package, column_name) value = get_column_value(work_package, column_name) return get_id_column_cell(work_package, value) if column_name == :id - return get_subject_column_cell(work_package, value) if with_descriptions? && column_name == :subject + return get_subject_column_cell(work_package, value) if is_report? && column_name == :subject escape_tags(value) end @@ -154,15 +154,39 @@ module WorkPackage::PDFExport::Common end end + def formatted_text_box_measured(formatted_text_array, options) + features_box = ::Prawn::Text::Formatted::Box.new(formatted_text_array, options.merge({ document: pdf })) + features_box.render + features_box.height + end + + def draw_horizontal_line(top, left, right, height, color) + pdf.stroke do + pdf.stroke_color = color + pdf.line_width = height + pdf.horizontal_line left, right, at: top + end + end + + def draw_styled_text(text, opts) + color_before = pdf.fill_color + @pdf.save_font do + @pdf.font(opts[:font], opts) if opts[:font] + @pdf.fill_color = opts[:color] if opts[:color] + @pdf.draw_text(text, opts) + end + pdf.fill_color = color_before + end + def draw_text_centered(text, text_style, top) text_width = measure_text_width(text, text_style) text_x = (pdf.bounds.width - text_width) / 2 - pdf.draw_text text, text_style.merge({ at: [text_x, top] }) + draw_styled_text text, text_style.merge({ at: [text_x, top] }) [text_x, text_width] end def draw_text_multiline_part(line, text_style, x_position, y_position) - pdf.draw_text line, text_style.merge({ at: [x_position, y_position] }) + draw_styled_text line, text_style.merge({ at: [x_position, y_position] }) measure_text_height(line, text_style) end @@ -197,7 +221,7 @@ module WorkPackage::PDFExport::Common lines = split_wrapped_lines(text, available_width, text_style) if lines.length > max_lines lines[max_lines - 1] = truncate_ellipsis(lines[max_lines - 1], available_width, text_style) - lines = lines.first(3) + lines = lines.first(max_lines) end lines end @@ -249,10 +273,14 @@ module WorkPackage::PDFExport::Common @group_sums[group] || {} end - def with_descriptions? + def is_report? options[:show_report] end + def with_cover? + false + end + def with_sums_table? query.display_sums? end @@ -271,6 +299,6 @@ module WorkPackage::PDFExport::Common end def current_page_nr - pdf.page_number + @page_count + pdf.page_number + @page_count - (with_cover? ? 1 : 0) end end diff --git a/app/models/work_package/pdf_export/cover.rb b/app/models/work_package/pdf_export/cover.rb new file mode 100644 index 00000000000..0ef197bf9ce --- /dev/null +++ b/app/models/work_package/pdf_export/cover.rb @@ -0,0 +1,136 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +## TODO move constants into style + +module WorkPackage::PDFExport::Cover + def write_cover_page! + top = pdf.bounds.top + logo_width = write_cover_logo(top) + write_cover_header(top, logo_width + styles.cover_logo_header_spacing) + write_cover_hr + write_cover_artwork + write_cover_footer + pdf.start_new_page + end + + def write_cover_artwork + max_width = pdf.bounds.width - styles.cover_art_padding_right + float_top = write_background_image + float_top -= write_artwork_headline(float_top, max_width) if project + float_top -= write_artwork_title(float_top, max_width) + write_artwork_subheading(float_top, max_width) unless User.current.nil? + end + + def available_title_height(current_y) + current_y - + styles.cover_art_headline_max_height - + styles.cover_art_subheading_max_height - + styles.cover_art_headline_spacing - + styles.cover_art_title_spacing + end + + def write_cover_hr + hr_style = styles.cover_header_border + draw_horizontal_line( + pdf.bounds.height - hr_style[:offset], + pdf.bounds.left, pdf.bounds.right, + hr_style[:height], hr_style[:color] + ) + end + + def write_cover_header(top, max_left) + draw_text_multiline_right( + text: heading.upcase, max_left:, + text_style: styles.cover_header, top: top + styles.cover_header_offset, max_lines: 1 + ) + end + + def write_artwork_headline(top, width) + text_style = styles.cover_art_headline + formatted_text_box_measured( + [text_style.merge({ text: project.name, size: nil, leading: nil })], + size: text_style[:size], leading: text_style[:leading], + at: [0, top], width:, height: styles.cover_art_headline_max_height, overflow: :shrink_to_fit + ) + styles.cover_art_headline_spacing + end + + def write_artwork_title(top, width) + max_title_height = available_title_height(top) + text_style = styles.cover_art_title + formatted_text_box_measured( + [text_style.merge({ text: heading, size: nil, leading: nil })], + size: text_style[:size], leading: text_style[:leading], + at: [0, top], width:, height: max_title_height, overflow: :shrink_to_fit + ) + styles.cover_art_title_spacing + end + + def write_artwork_subheading(top, width) + text_style = styles.cover_art_author + pdf.formatted_text_box( + [text_style.merge({ text: User.current.name, size: nil, leading: nil })], + size: text_style[:size], leading: text_style[:leading], + at: [0, top], width:, height: styles.cover_art_subheading_max_height, overflow: :shrink_to_fit) + end + + def write_cover_footer + draw_text_multiline_left( + text: footer_date, + max_left: pdf.bounds.width / 2, + max_lines: 1, + top: pdf.bounds.bottom - styles.cover_footer_offset, + text_style: styles.cover_footer + ) + end + + def write_cover_logo(top) + image_obj, image_info = logo_image + height = styles.cover_header_logo_height + scale = [height / image_info.height.to_f, 1].min + pdf.embed_image image_obj, image_info, { at: [0, top + height], scale: } + image_info.width.to_f * scale + end + + def cover_background_image + image_file = Rails.root.join("app/assets/images/pdf/cover.png") + image_obj, image_info = pdf.build_image_object(image_file) + scale = pdf.bounds.width / image_info.width.to_f + height = image_info.height.to_f * scale + image_opts = { at: [0, height], scale: } + [image_obj, image_info, image_opts, height] + end + + def write_background_image + height = pdf.bounds.height / 2 + pdf.canvas do + image_obj, image_info, image_opts, height = cover_background_image + pdf.embed_image image_obj, image_info, image_opts + end + height - styles.cover_art_padding_top + end +end diff --git a/app/models/work_package/pdf_export/page.rb b/app/models/work_package/pdf_export/page.rb index bcb57c9c4c4..3fafb8df487 100644 --- a/app/models/work_package/pdf_export/page.rb +++ b/app/models/work_package/pdf_export/page.rb @@ -42,7 +42,7 @@ module WorkPackage::PDFExport::Page image_obj, image_info, scale = logo_pdf_image top = logo_pdf_top left = logo_pdf_left(image_info.width.to_f * scale) - pdf.repeat :all do + pdf.repeat lambda { |pg| header_footer_filter_pages.exclude?(pg) } do pdf.embed_image image_obj, image_info, { at: [left, top], scale: } end end @@ -63,11 +63,16 @@ module WorkPackage::PDFExport::Page end def logo_pdf_image + image_obj, image_info = logo_image + scale = [styles.page_logo_height / image_info.height.to_f, 1].min + [image_obj, image_info, scale] + end + + def logo_image image_file = custom_logo_image image_file = Rails.root.join("app/assets/images/logo_openproject.png") if image_file.nil? image_obj, image_info = pdf.build_image_object(image_file) - scale = [styles.page_logo_height / image_info.height.to_f, 1].min - [image_obj, image_info, scale] + [image_obj, image_info] end def custom_logo_image @@ -92,8 +97,12 @@ module WorkPackage::PDFExport::Page write_logo! end + def header_footer_filter_pages + with_cover? ? [1] : [] + end + def write_footers! - pdf.repeat :all, dynamic: true do + pdf.repeat lambda { |pg| header_footer_filter_pages.exclude?(pg) }, dynamic: true do draw_footer_on_page end end @@ -122,6 +131,10 @@ module WorkPackage::PDFExport::Page end def total_page_nr_text - @total_page_nr ? "/#{@total_page_nr}" : '' + if @total_page_nr + "/#{@total_page_nr - (with_cover? ? 1 : 0)}" + else + '' + end end end diff --git a/app/models/work_package/pdf_export/style.rb b/app/models/work_package/pdf_export/style.rb index a61c543cf4f..b125a747fda 100644 --- a/app/models/work_package/pdf_export/style.rb +++ b/app/models/work_package/pdf_export/style.rb @@ -201,6 +201,70 @@ module WorkPackage::PDFExport::Style resolve_markdown_styling(@styles.dig(:work_package, :markdown) || {}) end + def cover_logo_header_spacing + 20 + end + + def cover_header + { font: 'SpaceMono', size: 10, color: '064e80' } + end + + def cover_header_offset + 6.5 + end + + def cover_header_logo_height + 25 + end + + def cover_footer + { size: 10, color: '414d5f' } + end + + def cover_footer_offset + 30 + end + + def cover_header_border + { color: 'd3dee3', height: 1, offset: 6 } + end + + def cover_art_padding_top + 120 + end + + def cover_art_padding_right + 150 + end + + def cover_art_headline_max_height + 30 + end + + def cover_art_subheading_max_height + 30 + end + + def cover_art_headline_spacing + 10 + end + + def cover_art_title_spacing + 14 + end + + def cover_art_headline + { font: 'SpaceMono', color: '414d5f', size: 10 } + end + + def cover_art_title + { color: '414d5f', styles: [:bold], size: 32, leading: -8 } + end + + def cover_art_author + { color: '414d5f', styles: [:italic], size: 10 } + end + private def resolve_pt(value, default) diff --git a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb index d8350d89d05..4fb004abdba 100644 --- a/app/models/work_package/pdf_export/work_package_list_to_pdf.rb +++ b/app/models/work_package/pdf_export/work_package_list_to_pdf.rb @@ -48,6 +48,7 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query include WorkPackage::PDFExport::TableOfContents include WorkPackage::PDFExport::Page include WorkPackage::PDFExport::Style + include WorkPackage::PDFExport::Cover attr_accessor :pdf, :options @@ -71,8 +72,8 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query rescue Prawn::Errors::CannotFit error(I18n.t(:error_pdf_export_too_many_columns)) rescue StandardError => e - Rails.logger.error { "Failed to generated PDF export: #{e} #{e.message}}." } - error(I18n.t(:error_pdf_failed_to_export, error: e.message)) + Rails.logger.error { "Failed to generated PDF export: #{e}." } + error(I18n.t(:error_pdf_failed_to_export, error: e.message[0..300])) end private @@ -80,7 +81,7 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query def setup_page! self.pdf = get_pdf(current_language) - configure_page_size!(with_descriptions? ? :portrait : :landscape) + configure_page_size!(is_report? ? :portrait : :landscape) end def render_work_packages(work_packages, filename: "pdf_export") @@ -99,11 +100,16 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query true end + def with_cover? + is_report? + end + def render_work_packages_pdfs(work_packages, filename) + write_cover_page! if with_cover? write_title! - write_work_packages_toc! work_packages, @id_wp_meta_map if with_descriptions? - write_work_packages_overview! work_packages, @id_wp_meta_map unless with_descriptions? - write_work_packages_sums! work_packages if with_sums_table? && with_descriptions? + write_work_packages_toc! work_packages, @id_wp_meta_map if is_report? + write_work_packages_overview! work_packages, @id_wp_meta_map unless is_report? + write_work_packages_sums! work_packages if with_sums_table? && is_report? if should_be_batched?(work_packages) render_batched(work_packages, filename) else @@ -151,7 +157,7 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query end def render_pdf(work_packages, filename) - write_work_packages_details!(work_packages, @id_wp_meta_map) if with_descriptions? + write_work_packages_details!(work_packages, @id_wp_meta_map) if is_report? write_after_pages! file = Tempfile.new(filename) pdf.render_file(file.path) @@ -216,7 +222,7 @@ class WorkPackage::PDFExport::WorkPackageListToPdf < WorkPackage::Exports::Query end def should_be_batched?(work_packages) - batch_supported? && with_descriptions? && with_images? && (work_packages.length > @work_packages_per_batch) + batch_supported? && is_report? && with_images? && (work_packages.length > @work_packages_per_batch) end def project