generate pdfs from images
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
io = StringIO.new
|
results =
|
||||||
|
template.schema.map do |item|
|
||||||
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
pdf = pdfs_index[item['attachment_uuid']]
|
attachment = save_signed_pdf(pdf:, submitter:, cert:, uuid: item['attachment_uuid'], name: item['name'])
|
||||||
|
|
||||||
pdf.sign(io, reason: "Signed by #{submitter.email}",
|
image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
||||||
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!(
|
attachment
|
||||||
uuid: item['attachment_uuid'],
|
end
|
||||||
blob: ActiveStorage::Blob.create_and_upload!(
|
|
||||||
io: StringIO.new(io.string), filename: "#{item['name']}.pdf"
|
return results if image_pdfs.size < 2
|
||||||
),
|
|
||||||
name: 'documents',
|
images_pdf =
|
||||||
record: submitter
|
image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
|
||||||
|
pdf.pages.each { |page| doc.pages << doc.import(page) }
|
||||||
|
end
|
||||||
|
|
||||||
|
images_pdf_result =
|
||||||
|
save_signed_pdf(
|
||||||
|
pdf: images_pdf,
|
||||||
|
submitter:,
|
||||||
|
cert:,
|
||||||
|
uuid: images_pdf_uuid(original_documents.select(&:image?)),
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user