opf/openproject

View on GitHub
app/models/work_package/pdf_export/style.rb

Summary

Maintainability
C
1 day
Test Coverage
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2024 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.
#++

module WorkPackage::PDFExport::Style
  include MarkdownToPDF::StyleValidation

  class PDFStyles
    include MarkdownToPDF::Common
    include MarkdownToPDF::StyleHelper

    def initialize(yml)
      @styles = yml.deep_symbolize_keys
    end

    def page_size
      @styles[:page_size] || "EXECUTIVE"
    end

    def page_header_offset
      resolve_pt(@styles.dig(:page_header, :offset), 20)
    end

    def page_footer_offset
      resolve_pt(@styles.dig(:page_footer, :offset), -30)
    end

    def page_footer_horizontal_spacing
      resolve_pt(@styles.dig(:page_footer, :spacing), 6)
    end

    def page_logo_height
      resolve_pt(@styles.dig(:page_logo, :height), 20)
    end

    def page_logo_align
      @styles.dig(:page_logo, :align) || :right
    end

    def page_margin_top
      resolve_pt(@styles.dig(:page, :margin_top), 60)
    end

    def page_margin_left
      resolve_pt(@styles.dig(:page, :margin_left), 50)
    end

    def page_margin_right
      resolve_pt(@styles.dig(:page, :margin_right), 50)
    end

    def page_margin_bottom
      resolve_pt(@styles.dig(:page, :margin_bottom), 60)
    end

    def page_heading
      resolve_font(@styles[:page_heading])
    end

    def page_heading_margins
      resolve_margin(@styles[:page_heading])
    end

    def page_header
      resolve_font(@styles[:page_header])
    end

    def page_footer
      resolve_font(@styles[:page_footer])
    end

    def page_break_threshold
      resolve_pt(@styles.dig(:page, :page_break_threshold), 200)
    end

    def link_color
      @styles.dig(:page, :link_color) || "000000"
    end

    def overview_group_header
      resolve_font(@styles.dig(:overview, :group_heading))
    end

    def overview_group_header_margins
      resolve_margin(@styles.dig(:overview, :group_heading))
    end

    def overview_table_margins
      resolve_margin(@styles.dig(:overview, :table))
    end

    def overview_table_cell
      resolve_table_cell(@styles.dig(:overview, :table, :cell))
    end

    def overview_table_header_cell
      overview_table_cell.merge(
        resolve_table_cell(@styles.dig(:overview, :table, :cell_header))
      )
    end

    def overview_table_sums_cell
      overview_table_cell.merge(
        resolve_table_cell(@styles.dig(:overview, :table, :cell_sums))
      )
    end

    def overview_table_subject_indent
      resolve_pt(@styles.dig(:overview, :table, :subject_indent), 0)
    end

    def toc_max_depth
      @styles.dig(:toc, :max_depth) || 4
    end

    def toc_margins
      resolve_margin(@styles[:toc])
    end

    def toc_indent_mode
      @styles.dig(:toc, :indent_mode)
    end

    def toc_item(level)
      resolve_font(@styles.dig(:toc, :item)).merge(
        resolve_font(@styles.dig(:toc, :"item_level_#{level}"))
      )
    end

    def toc_item_subject_indent
      resolve_pt(@styles.dig(:toc, :subject_indent), 4)
    end

    def toc_item_margins(level)
      resolve_margin(@styles.dig(:toc, :item)).merge(
        resolve_margin(@styles.dig(:toc, :"item_level_#{level}"))
      )
    end

    def wp_margins
      resolve_margin(@styles[:work_package])
    end

    def wp_subject(level)
      resolve_font(@styles.dig(:work_package, :subject)).merge(
        resolve_font(@styles.dig(:work_package, :"subject_level_#{level}"))
      )
    end

    def wp_detail_subject_margins
      resolve_margin(@styles.dig(:work_package, :subject))
    end

    def wp_attributes_table_margins
      resolve_margin(@styles.dig(:work_package, :attributes_table))
    end

    def wp_attributes_table_cell
      resolve_table_cell(@styles.dig(:work_package, :attributes_table, :cell))
    end

    def wp_attributes_table_label_cell
      wp_attributes_table_cell.merge(
        resolve_table_cell(@styles.dig(:work_package, :attributes_table, :cell_label))
      )
    end

    def wp_markdown_label
      resolve_font(@styles.dig(:work_package, :markdown_label))
    end

    def wp_markdown_label_margins
      resolve_margin(@styles.dig(:work_package, :markdown_label))
    end

    def wp_markdown_margins
      resolve_margin(@styles.dig(:work_package, :markdown_margin))
    end

    def wp_markdown_styling_yml
      resolve_markdown_styling(@styles.dig(:work_package, :markdown) || {})
    end

    def cover_header
      resolve_font(@styles.dig(:cover, :header))
    end

    def cover_header_logo_height
      resolve_pt(@styles.dig(:cover, :header, :logo_height), 25)
    end

    def cover_header_border
      { color: @styles.dig(:cover, :header, :border, :color),
        height: resolve_pt(@styles.dig(:cover, :header, :border, :height), 1),
        offset: resolve_pt(@styles.dig(:cover, :header, :border, :offset), 0) }
    end

    def cover_footer
      resolve_font(@styles.dig(:cover, :footer))
    end

    def cover_footer_offset
      resolve_pt(@styles.dig(:cover, :footer, :offset), 0)
    end

    def cover_hero_padding
      resolve_padding(@styles.dig(:cover, :hero))
    end

    def cover_hero_title
      resolve_font(@styles.dig(:cover, :hero, :title))
    end

    def cover_hero_title_spacing
      resolve_pt(@styles.dig(:cover, :hero, :title, :spacing), 0)
    end

    def cover_hero_title_max_height
      resolve_pt(@styles.dig(:cover, :hero, :title, :max_height), 30)
    end

    def cover_hero_heading
      resolve_font(@styles.dig(:cover, :hero, :heading))
    end

    def cover_hero_heading_spacing
      resolve_pt(@styles.dig(:cover, :hero, :heading, :spacing), 0)
    end

    def cover_hero_subheading
      resolve_font(@styles.dig(:cover, :hero, :subheading))
    end

    def cover_hero_subheading_max_height
      resolve_pt(@styles.dig(:cover, :hero, :subheading, :max_height), 30)
    end

    private

    def resolve_pt(value, default)
      parse_pt(value) || default
    end

    def resolve_table_cell(style)
      # prawn.table.make_cell does use differently named options
      # so to have them specified consistently, we map here
      opts = opts_table_cell(style || {})
      font_styles = opts.delete(:styles) || []
      opts[:font_style] = font_styles[0] unless font_styles.empty?
      color = opts.delete(:color)
      opts[:text_color] = color unless color.nil?
      opts
    end

    def resolve_markdown_styling(style)
      page = style.delete(:font)
      style[:page] = page unless page.nil?
      style
    end

    def resolve_font(style)
      opts_font(style || {})
    end

    def resolve_margin(style)
      opts_margin(style || {})
    end

    def resolve_padding(style)
      opts_padding(style || {})
    end
  end

  def styles
    @styles ||= PDFStyles.new(load_style)
  end

  private

  def load_style
    yml = YAML::load_file(File.join(styles_asset_path, "standard.yml"))
    schema = JSON::load_file(File.join(styles_asset_path, "schema.json"))
    validate_schema!(yml, schema)
  end

  def styles_asset_path
    # TODO: where to put & load yml & json file
    File.dirname(File.expand_path(__FILE__))
  end
end