generate pdfs from images

pull/105/head
Alex Turchyn 2 years ago
parent 1da67011c4
commit 92e05ae8e9

@ -8,6 +8,15 @@ class SubmissionsDownloadController < ApplicationController
Submissions::GenerateResultAttachments.call(submitter) if submitter.documents.blank? Submissions::GenerateResultAttachments.call(submitter) if submitter.documents.blank?
render json: submitter.documents.map { |e| helpers.rails_blob_url(e) } original_documents = submitter.submission.template.documents.preload(:blob)
is_more_than_two_images = original_documents.count(&:image?) > 1
urls = submitter.documents.preload(:blob).filter_map do |attachment|
next if is_more_than_two_images && original_documents.find { |a| a.uuid == attachment.uuid }&.image?
helpers.rails_blob_url(attachment)
end
render json: urls
end end
end end

@ -18,20 +18,27 @@ export default targetable(class extends HTMLElement {
this.toggleState() this.toggleState()
fetch(this.dataset.src).then((response) => response.json()).then((urls) => { fetch(this.dataset.src).then((response) => response.json()).then((urls) => {
urls.forEach((url) => { const fileRequests = urls.map((url) => {
fetch(url).then(async (resp) => { return () => {
const blobUrl = URL.createObjectURL(await resp.blob()) return fetch(url).then(async (resp) => {
const link = document.createElement('a') const blobUrl = URL.createObjectURL(await resp.blob())
const link = document.createElement('a')
link.href = blobUrl link.href = blobUrl
link.setAttribute('download', resp.headers.get('content-disposition').split('"')[1]) link.setAttribute('download', decodeURI(resp.headers.get('content-disposition').split('"')[1]))
link.click() link.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
}) })
}
}) })
}).finally(() => {
fileRequests.reduce(
(prevPromise, request) => prevPromise.then(() => request()),
Promise.resolve()
)
this.toggleState() this.toggleState()
}) })
} }

@ -90,20 +90,27 @@ export default {
this.isDownloading = true this.isDownloading = true
fetch(`/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => { fetch(`/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => {
urls.forEach((url) => { const fileRequests = urls.map((url) => {
fetch(url).then(async (resp) => { return () => {
const blobUrl = URL.createObjectURL(await resp.blob()) return fetch(url).then(async (resp) => {
const link = document.createElement('a') const blobUrl = URL.createObjectURL(await resp.blob())
const link = document.createElement('a')
link.href = blobUrl link.href = blobUrl
link.setAttribute('download', resp.headers.get('content-disposition').split('"')[1]) link.setAttribute('download', decodeURI(resp.headers.get('content-disposition').split('"')[1]))
link.click() link.click()
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
}) })
}
}) })
}).finally(() => {
fileRequests.reduce(
(prevPromise, request) => prevPromise.then(() => request()),
Promise.resolve()
)
this.isDownloading = false this.isDownloading = false
}) })
} }

@ -191,7 +191,7 @@
:field="currentField" :field="currentField"
:attachments-index="attachmentsIndex" :attachments-index="attachmentsIndex"
:submitter-slug="submitterSlug" :submitter-slug="submitterSlug"
@attached="attachments.push($event)" @attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
/> />
<SignatureStep <SignatureStep
v-else-if="currentField.type === 'signature'" v-else-if="currentField.type === 'signature'"
@ -208,7 +208,7 @@
:field="currentField" :field="currentField"
:attachments-index="attachmentsIndex" :attachments-index="attachmentsIndex"
:submitter-slug="submitterSlug" :submitter-slug="submitterSlug"
@attached="attachments.push($event)" @attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]"
/> />
</div> </div>
<div class="mt-8"> <div class="mt-8">

@ -5,6 +5,8 @@ module Submissions
FONT_SIZE = 11 FONT_SIZE = 11
FONT_NAME = 'Helvetica' FONT_NAME = 'Helvetica'
A4_SIZE = [595, 842].freeze
module_function module_function
# rubocop:disable Metrics # rubocop:disable Metrics
@ -126,42 +128,101 @@ module Submissions
end end
end end
template.schema.map do |item| image_pdfs = []
template.documents.find { |a| a.uuid == item['attachment_uuid'] } original_documents = template.documents.preload(:blob)
results =
template.schema.map do |item|
pdf = pdfs_index[item['attachment_uuid']]
attachment = save_signed_pdf(pdf:, submitter:, cert:, uuid: item['attachment_uuid'], name: item['name'])
io = StringIO.new image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
pdf = pdfs_index[item['attachment_uuid']] attachment
end
return results if image_pdfs.size < 2
pdf.sign(io, reason: "Signed by #{submitter.email}", images_pdf =
certificate: OpenSSL::X509::Certificate.new(cert['cert']), image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
key: OpenSSL::PKey::RSA.new(cert['key']), pdf.pages.each { |page| doc.pages << doc.import(page) }
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']), end
OpenSSL::X509::Certificate.new(cert['root_ca'])])
ActiveStorage::Attachment.create!( images_pdf_result =
uuid: item['attachment_uuid'], save_signed_pdf(
blob: ActiveStorage::Blob.create_and_upload!( pdf: images_pdf,
io: StringIO.new(io.string), filename: "#{item['name']}.pdf" submitter:,
), cert:,
name: 'documents', uuid: images_pdf_uuid(original_documents.select(&:image?)),
record: submitter name: template.name
) )
end
results + [images_pdf_result]
end end
# rubocop:enable Metrics # rubocop:enable Metrics
def save_signed_pdf(pdf:, submitter:, cert:, uuid:, name:)
io = StringIO.new
pdf.sign(io, reason: "Signed by #{submitter.email}",
certificate: OpenSSL::X509::Certificate.new(cert['cert']),
key: OpenSSL::PKey::RSA.new(cert['key']),
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
OpenSSL::X509::Certificate.new(cert['root_ca'])])
ActiveStorage::Attachment.create!(
uuid:,
blob: ActiveStorage::Blob.create_and_upload!(
io: StringIO.new(io.string), filename: "#{name}.pdf"
),
name: 'documents',
record: submitter
)
end
def images_pdf_uuid(attachments)
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':'))
end
def build_pdfs_index(submitter) def build_pdfs_index(submitter)
latest_submitter = submitter.submission.submitters latest_submitter = submitter.submission.submitters
.select { |e| e.id != submitter.id && e.completed_at? } .select { |e| e.id != submitter.id && e.completed_at? }
.max_by(&:completed_at) .max_by(&:completed_at)
documents = latest_submitter&.documents.to_a.presence documents = latest_submitter&.documents&.preload(:blob).to_a.presence
documents ||= submitter.submission.template.documents documents ||= submitter.submission.template.documents.preload(:blob)
documents.to_h do |attachment| documents.to_h do |attachment|
[attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))] pdf =
if attachment.image?
build_pdf_from_image(attachment)
else
HexaPDF::Document.new(io: StringIO.new(attachment.download))
end
[attachment.uuid, pdf]
end end
end end
def build_pdf_from_image(attachment)
pdf = HexaPDF::Document.new
page = pdf.pages.add
scale = [A4_SIZE.first / attachment.metadata['width'].to_f,
A4_SIZE.last / attachment.metadata['height'].to_f].min
page.box.width = attachment.metadata['width'] * scale
page.box.height = attachment.metadata['height'] * scale
page.canvas.image(
StringIO.new(attachment.download),
at: [0, 0],
width: page.box.width,
height: page.box.height
)
pdf
end
end end
end end

Loading…
Cancel
Save