scinote-web/app/services/reports/docx_renderer.rb

180 lines
6 KiB
Ruby

# frozen_string_literal: true
module Reports
class DocxRenderer
def self.render_p_element(docx, element, options = {})
docx.p do
element[:children].each do |text_el|
if text_el[:type] == 'text'
style = text_el[:style] || {}
text text_el[:value], style
text ' ' if text_el[:value] != ''
elsif text_el[:type] == 'br' && !options[:skip_br]
br
elsif text_el[:type] == 'a'
Reports::DocxRenderer.render_link_element(self, text_el, options)
elsif text_el[:type] == 'sup'
text text_el[:value] do
vertical_align 'superscript'
end
elsif text_el[:type] == 'sub'
text text_el[:value] do
vertical_align 'subscript'
end
end
end
end
end
def self.render_link_element(node, link_item, options = {})
scinote_url = options[:scinote_url]
link_style = options[:link_style]
if link_item[:link]
link_url = Reports::Utils.link_prepare(scinote_url, link_item[:link])
node.link link_item[:value], link_url, link_style
else
node.text link_item[:value], link_style
end
node.text ' ' if link_item[:value] != ''
end
def self.render_img_element(docx, element, options = {})
style = element[:style]
if options[:table]
max_width = (style[:max_width] / options[:table][:columns].to_f)
if style[:width] > max_width
style[:height] = (max_width / style[:width].to_f) * style[:height]
style[:width] = max_width
end
end
docx.img element[:data] do
data element[:blob].download
width style[:width]
height style[:height]
align style[:align] || :left
end
end
def self.render_list_element(docx, element, options = {})
bookmark_items = Reports::DocxRenderer.recursive_list_items_renderer(docx, element)
bookmark_items.each_with_index do |(key, item), index|
if item[:type] == 'image'
docx.bookmark_start id: index, name: key
docx.p do
br
text item[:blob]&.filename.to_s
end
Reports::DocxRenderer.render_img_element(docx, item)
docx.bookmark_end id: index
elsif item[:type] == 'table'
docx.bookmark_start id: index, name: key
# Bookmark won't work with table only, empty p element added
docx.p do
br
text ''
end
Reports::DocxRenderer.render_table_element(docx, item, options)
docx.bookmark_end id: index
end
end
end
# rubocop:disable Metrics/BlockLength
def self.recursive_list_items_renderer(node, element, bookmark_items: {})
node.public_send(element[:type]) do
element[:data].each do |values_array|
li do
values_array.each do |item|
case item
when Hash
if %w(ul ol li).include?(item[:type])
Reports::DocxRenderer.recursive_list_items_renderer(self, item, bookmark_items: bookmark_items)
elsif %w(a).include?(item[:type])
Reports::DocxRenderer.render_link_element(self, item)
elsif %w(image).include?(item[:type])
bookmark_items[item[:bookmark_id]] = item
link I18n.t('projects.reports.renderers.lists.appended_image',
name: item[:blob]&.filename), item[:bookmark_id] do
internal true
end
elsif %w(table).include?(item[:type])
bookmark_items[item[:bookmark_id]] = item
link I18n.t('projects.reports.renderers.lists.appended_table'), item[:bookmark_id] do
internal true
end
elsif %w(text).include?(item[:type])
# TODO: Text with styles, not working yet.
style = item[:style] || {}
text item[:value], style
end
else
text item
end
end
end
end
end
bookmark_items
end
# rubocop:enable Metrics/BlockLength
def self.render_table_element(docx, element, options = {})
docx_table = []
element[:data].each do |row|
docx_row = []
row[:data].each do |cell|
docx_cell = Caracal::Core::Models::TableCellModel.new do |c|
cell.each do |content|
if content[:type] == 'p'
Reports::DocxRenderer.render_p_element(c, content, options.merge({ skip_br: true }))
elsif content[:type] == 'table'
Reports::DocxRenderer.render_table_element(c, content, options)
elsif content[:type] == 'image'
Reports::DocxRenderer.render_img_element(c, content, table: { columns: row[:data].length })
end
end
end
docx_row.push(docx_cell)
end
docx_table.push(docx_row)
end
docx.table docx_table, border_size: Constants::REPORT_DOCX_TABLE_BORDER_SIZE
end
def self.render_asset_image(docx, asset)
return unless asset
asset_preview = Reports::Utils.image_prepare(asset)
dimension = FastImage.size(asset_preview.processed.service_url)
return unless dimension
x = dimension[0]
y = dimension[1]
if x > 300
y = y * 300 / x
x = 300
end
blob_data = if asset_preview.instance_of? ActiveStorage::Preview
asset_preview.image.download
else
asset_preview.blob.download
end
docx.img asset_preview.processed.service_url.split('&')[0] do
data blob_data
width x
height y
end
rescue SocketError, Caracal::Errors::InvalidModelError => e # invalid URL or broken image
Rails.logger.warn("Unable to render docx image due to #{e.class}: #{e}")
nil
end
end
end