93143c28f2
see https://github.com/foodcoops/foodsoft/pull/907 for reference and original work by viehlieb Co-authored-by: viehlieb <pf@pragma-shift.net> fix PDF Pdf make explicit deposit in invoices work add ordergroupname to invoice file name mark bold sum for vat exempt foodcoops download multiple group order invoice as zip
170 lines
4.7 KiB
Ruby
170 lines
4.7 KiB
Ruby
require 'prawn/measurement_extensions'
|
|
|
|
class RotatedCell < Prawn::Table::Cell::Text
|
|
def initialize(pdf, text, options = {})
|
|
options[:content] = text
|
|
options[:valign] = :center
|
|
options[:align] = :center
|
|
options[:rotate_around] = :center
|
|
@rotation = -options[:rotate] || 0
|
|
super(pdf, [0, pdf.cursor], options)
|
|
end
|
|
|
|
def tan_rotation
|
|
Math.tan(Math::PI * @rotation / 180)
|
|
end
|
|
|
|
def skew
|
|
(height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation
|
|
end
|
|
|
|
def styled_width_of(_text)
|
|
options = @text_options.reject { |k| k == :style }
|
|
with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation }
|
|
end
|
|
|
|
def natural_content_height
|
|
options = @text_options.reject { |k| k == :style }
|
|
with_font { (@pdf.width_of(@content, options) + padding_top + padding_bottom) * tan_rotation }
|
|
end
|
|
|
|
def draw_borders(point)
|
|
@pdf.mask(:line_width, :stroke_color) do
|
|
x, y = point
|
|
from = [[x - skew, y + (border_top_width / 2.0)],
|
|
to = [x, y - height - (border_bottom_width / 2.0)]]
|
|
|
|
@pdf.line_width = @border_widths[3]
|
|
@pdf.stroke_color = @border_colors[3]
|
|
@pdf.stroke_line(from, to)
|
|
@pdf.undash
|
|
end
|
|
end
|
|
|
|
def draw_content
|
|
with_font do
|
|
with_text_color do
|
|
text_box(width: spanned_content_width + FPTolerance + skew,
|
|
height: spanned_content_height + FPTolerance,
|
|
at: [1 - skew, @pdf.cursor]).render
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class RenderPdf < Prawn::Document
|
|
include ActionView::Helpers::NumberHelper
|
|
include ApplicationHelper
|
|
|
|
TOP_MARGIN = 36
|
|
BOTTOM_MARGIN = 23
|
|
HEADER_SPACE = 9
|
|
FOOTER_SPACE = 3
|
|
HEADER_FONT_SIZE = 16
|
|
FOOTER_FONT_SIZE = 8
|
|
DEFAULT_FONT = 'OpenSans'
|
|
|
|
def initialize(options = {})
|
|
options[:font_size] ||= FoodsoftConfig[:pdf_font_size].try(:to_f) || 12
|
|
options[:page_size] ||= FoodsoftConfig[:pdf_page_size] || 'A4'
|
|
options[:skip_page_creation] = true
|
|
@options = options
|
|
@first_page = true
|
|
no_footer = @options&.[](:no_footer) ? true : false
|
|
super(options)
|
|
|
|
# Use ttf for better utf-8 compability
|
|
font_families.update(
|
|
'OpenSans' => {
|
|
bold: font_path('OpenSans-Bold.ttf'),
|
|
italic: font_path('OpenSans-Italic.ttf'),
|
|
bold_italic: font_path('OpenSans-BoldItalic.ttf'),
|
|
normal: font_path('OpenSans-Regular.ttf')
|
|
}
|
|
)
|
|
|
|
header = options[:title] || title
|
|
footer = I18n.l(Time.now, format: :long) unless no_footer
|
|
|
|
header_size = 0
|
|
header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header
|
|
footer_size = no_footer ? 0 : height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
|
|
|
|
start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size)
|
|
|
|
font DEFAULT_FONT
|
|
|
|
repeat :all, dynamic: true do
|
|
bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do
|
|
text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header
|
|
end
|
|
|
|
unless no_footer
|
|
font_size FOOTER_FONT_SIZE do
|
|
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
|
text footer, align: :left, valign: :bottom
|
|
end
|
|
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
|
text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def title
|
|
nil
|
|
end
|
|
|
|
def to_pdf
|
|
body # Add content, which is defined in subclasses
|
|
render # Render pdf
|
|
end
|
|
|
|
# @todo avoid underscore instead of unicode whitespace in pdf :/
|
|
def number_to_currency(number, options = {})
|
|
super(number, options).gsub("\u202f", ' ') if number
|
|
end
|
|
|
|
def font_size(points = nil, &block)
|
|
points *= @options[:font_size] / 12 if points
|
|
super(points, &block)
|
|
end
|
|
|
|
# add pagebreak or vertical whitespace, depending on configuration
|
|
def down_or_page(space = 10)
|
|
if @first_page
|
|
@first_page = false
|
|
return
|
|
end
|
|
if pdf_add_page_breaks?
|
|
start_new_page
|
|
else
|
|
move_down space
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def fontsize(size)
|
|
size
|
|
end
|
|
|
|
# return whether pagebreak or vertical whitespace is used for breaks
|
|
def pdf_add_page_breaks?(docid = nil)
|
|
docid ||= self.class.name.underscore
|
|
cfg = FoodsoftConfig[:pdf_add_page_breaks]
|
|
case cfg
|
|
when Array
|
|
cfg.index(docid.to_s).any?
|
|
when Hash
|
|
cfg[docid.to_s]
|
|
else
|
|
cfg
|
|
end
|
|
end
|
|
|
|
def font_path(name)
|
|
Rails.root.join('vendor', 'assets', 'fonts', name)
|
|
end
|
|
end
|