fix pdf generation
This commit is contained in:
@@ -5,7 +5,7 @@ module Api
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug]) unless current_account
|
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
blob = ActiveStorage::Blob.find_signed(params[:blob_signed_id])
|
blob = ActiveStorage::Blob.find_signed(params[:blob_signed_id])
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class SubmissionsController < ApplicationController
|
|||||||
submissions_params[:submission].to_h.map do |_, attrs|
|
submissions_params[:submission].to_h.map do |_, attrs|
|
||||||
submission = @template.submissions.new
|
submission = @template.submissions.new
|
||||||
|
|
||||||
attrs[:submitters].each do |_, submitter_attrs|
|
attrs[:submitters].each do |submitter_attrs|
|
||||||
submission.submitters.new(**submitter_attrs, sent_at: params[:send_email] == '1' ? Time.current : nil)
|
submission.submitters.new(**submitter_attrs, sent_at: params[:send_email] == '1' ? Time.current : nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class SubmissionsController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: %i[uuid email] })
|
params.permit(submission: { submitters: [%i[uuid email]] })
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_template
|
def load_template
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ class SubmissionsDebugController < ApplicationController
|
|||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html do
|
f.html do
|
||||||
render 'submit_template/show'
|
render 'submit_form/show'
|
||||||
end
|
end
|
||||||
f.pdf do
|
f.pdf do
|
||||||
Submissions::GenerateResultAttachments.call(@submitter.submission)
|
Submissions::GenerateResultAttachments.call(@submitter)
|
||||||
|
|
||||||
send_data ActiveStorage::Attachment.where(name: :documents).last.download,
|
send_data ActiveStorage::Attachment.where(name: :documents).last.download,
|
||||||
filename: 'debug.pdf',
|
filename: 'debug.pdf',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { targets, targetable } from '@github/catalyst/lib/targetable'
|
|||||||
export default actionable(targetable(class extends HTMLElement {
|
export default actionable(targetable(class extends HTMLElement {
|
||||||
static [targets.static] = ['items']
|
static [targets.static] = ['items']
|
||||||
|
|
||||||
addItem = (e) => {
|
addItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const originalItem = this.items[0]
|
const originalItem = this.items[0]
|
||||||
@@ -26,8 +26,9 @@ export default actionable(targetable(class extends HTMLElement {
|
|||||||
originalItem.parentNode.append(duplicateItem)
|
originalItem.parentNode.append(duplicateItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItem = (e) => {
|
removeItem (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
this.items.find((item) => item.contains(e.target))?.remove()
|
this.items.find((item) => item.contains(e.target))?.remove()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-else-if="field.type === 'file'"
|
v-else-if="field.type === 'file'"
|
||||||
class="px-0.5 flex items-center"
|
class="px-0.5 flex flex-col justify-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
v-for="(attachment, index) in attachments"
|
v-for="(attachment, index) in attachments"
|
||||||
@@ -73,9 +73,8 @@
|
|||||||
:checked="!!modelValue"
|
:checked="!!modelValue"
|
||||||
@click="$emit('update:model-value', !modelValue)"
|
@click="$emit('update:model-value', !modelValue)"
|
||||||
>
|
>
|
||||||
<component
|
<IconCheck
|
||||||
:is="modelValue ? 'IconCheckbox' : 'IconSquare'"
|
v-else-if="modelValue"
|
||||||
v-else
|
|
||||||
class="aspect-square"
|
class="aspect-square"
|
||||||
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
:class="{ '!w-auto !h-full': area.w > area.h, '!w-full !h-auto': area.w <= area.h }"
|
||||||
/>
|
/>
|
||||||
@@ -98,14 +97,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconSquare } from '@tabler/icons-vue'
|
import { IconTextSize, IconWriting, IconCalendarEvent, IconPhoto, IconCheckbox, IconPaperclip, IconSelect, IconCircleDot, IconChecks, IconCheck } from '@tabler/icons-vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldArea',
|
name: 'FieldArea',
|
||||||
components: {
|
components: {
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
IconCheckbox,
|
IconCheck
|
||||||
IconSquare
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
field: {
|
field: {
|
||||||
|
|||||||
@@ -91,13 +91,12 @@ export default {
|
|||||||
|
|
||||||
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) => {
|
urls.forEach((url) => {
|
||||||
fetch(url).then(async (response) => {
|
fetch(url).then(async (resp) => {
|
||||||
const blob = new Blob([await response.text()], { type: `${response.headers.get('content-type')};charset=utf-8;` })
|
const blobUrl = URL.createObjectURL(await resp.blob())
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
|
|
||||||
link.href = url
|
link.href = blobUrl
|
||||||
link.setAttribute('download', response.headers.get('content-disposition').split('"')[1])
|
link.setAttribute('download', resp.headers.get('content-disposition').split('"')[1])
|
||||||
|
|
||||||
link.click()
|
link.click()
|
||||||
|
|
||||||
|
|||||||
@@ -16,13 +16,13 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid md:grid-cols-2 gap-4">
|
<div class="grid md:grid-cols-2 gap-4">
|
||||||
<% @template.submitters.each.with_index(1) do |item, index| %>
|
<% @template.submitters.each do |item| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text"> <%= item['name'] %></span>
|
<span class="label-text"> <%= item['name'] %></span>
|
||||||
</label>
|
</label>
|
||||||
<input type="hidden" name="submission[1][submitters][<%= index %>][uuid]" value="<%= item['uuid'] %>" >
|
<input type="hidden" name="submission[1][submitters][][uuid]" value="<%= item['uuid'] %>" >
|
||||||
<input type="email" name="submission[1][submitters][<%= index %>][email]" value="<%= item['email'] %>" class="input input-sm input-bordered" placeholder="Email" required>
|
<input type="email" name="submission[1][submitters][][email]" value="<%= item['email'] %>" class="input input-sm input-bordered" placeholder="Email" required>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
module PdfIcons
|
module PdfIcons
|
||||||
PATH = Rails.root.join('lib/pdf_icons')
|
PATH = Rails.root.join('lib/pdf_icons')
|
||||||
|
|
||||||
|
WIDTH = 240
|
||||||
|
HEIGHT = 240
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def check_io
|
def check_io
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
module Submissions
|
module Submissions
|
||||||
module GenerateResultAttachments
|
module GenerateResultAttachments
|
||||||
FONT_SIZE = 12
|
FONT_SIZE = 11
|
||||||
FONT_NAME = 'Helvetica'
|
FONT_NAME = 'Helvetica'
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(submitter)
|
def call(submitter)
|
||||||
|
layouter = HexaPDF::Layout::TextLayouter.new(valign: :center)
|
||||||
template = submitter.submission.template
|
template = submitter.submission.template
|
||||||
|
|
||||||
cert = submitter.submission.template.account.encrypted_configs
|
cert = submitter.submission.template.account.encrypted_configs
|
||||||
@@ -36,61 +37,91 @@ module Submissions
|
|||||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||||
io = StringIO.new(attachment.download)
|
io = StringIO.new(attachment.download)
|
||||||
|
|
||||||
Vips::Image.new_from_buffer(io.read, '')
|
|
||||||
|
|
||||||
scale = [(area['w'] * width) / attachment.metadata['width'],
|
scale = [(area['w'] * width) / attachment.metadata['width'],
|
||||||
(area['h'] * height) / attachment.metadata['height']].min
|
(area['h'] * height) / attachment.metadata['height']].min
|
||||||
|
|
||||||
canvas.image(io, at: [area['x'] * width,
|
canvas.image(
|
||||||
height - (area['y'] * height) -
|
io,
|
||||||
(((attachment.metadata['height'] * scale) + (area['h'] * height)) / 2)],
|
at: [
|
||||||
|
(area['x'] * width) + (area['w'] * width / 2) - ((attachment.metadata['width'] * scale) / 2),
|
||||||
|
height - (area['y'] * height) - (attachment.metadata['height'] * scale / 2) - (area['h'] * height / 2)
|
||||||
|
],
|
||||||
width: attachment.metadata['width'] * scale,
|
width: attachment.metadata['width'] * scale,
|
||||||
height: attachment.metadata['height'] * scale)
|
height: attachment.metadata['height'] * scale
|
||||||
|
)
|
||||||
when 'file'
|
when 'file'
|
||||||
Array.wrap(value).each_with_index do |uuid, index|
|
page[:Annots] ||= []
|
||||||
|
|
||||||
|
items = Array.wrap(value).each_with_object([]) do |uuid, acc|
|
||||||
attachment = submitter.attachments.find { |a| a.uuid == uuid }
|
attachment = submitter.attachments.find { |a| a.uuid == uuid }
|
||||||
|
|
||||||
canvas.image(PdfIcons.paperclip_io,
|
acc << HexaPDF::Layout::InlineBox.create(width: FONT_SIZE, height: FONT_SIZE,
|
||||||
at: [area['x'] * width,
|
margin: [0, 1, -2, 0]) do |cv, box|
|
||||||
height - ((area['y'] * height) + (1.2 * FONT_SIZE) - (FONT_SIZE * index))],
|
cv.image(PdfIcons.paperclip_io, at: [0, 0], width: box.content_width)
|
||||||
width: FONT_SIZE, height: FONT_SIZE)
|
end
|
||||||
|
|
||||||
canvas.font(FONT_NAME, size: FONT_SIZE)
|
acc << HexaPDF::Layout::TextFragment.create("#{attachment.filename}\n", font: pdf.fonts.add(FONT_NAME),
|
||||||
canvas.text(attachment.filename.to_s,
|
font_size: FONT_SIZE)
|
||||||
at: [(area['x'] * width) + FONT_SIZE,
|
end
|
||||||
height - ((area['y'] * height) + FONT_SIZE - (FONT_SIZE * index))])
|
|
||||||
|
|
||||||
page[:Annots] ||= []
|
lines = layouter.fit(items, area['w'] * width, height).lines
|
||||||
page[:Annots] << pdf.add({
|
|
||||||
|
box_height = lines.sum(&:height)
|
||||||
|
height_diff = [0, box_height - (area['h'] * height)].max
|
||||||
|
|
||||||
|
lines.each_with_index.reduce(0) do |acc, (line, index)|
|
||||||
|
next acc unless line.items.first.is_a?(HexaPDF::Layout::InlineBox)
|
||||||
|
|
||||||
|
attachment_uuid = Array.wrap(value)[acc]
|
||||||
|
attachment = submitter.attachments.find { |a| a.uuid == attachment_uuid }
|
||||||
|
|
||||||
|
next_index =
|
||||||
|
lines[(index + 1)..].index { |l| l.items.first.is_a?(HexaPDF::Layout::InlineBox) } || (lines.size - 1)
|
||||||
|
|
||||||
|
page[:Annots] << pdf.add(
|
||||||
|
{
|
||||||
Type: :Annot, Subtype: :Link,
|
Type: :Annot, Subtype: :Link,
|
||||||
Rect: [
|
Rect: [
|
||||||
area['x'] * width,
|
area['x'] * width,
|
||||||
height - (area['y'] * height),
|
height - (area['y'] * height) - lines[...index].sum(&:height) + height_diff,
|
||||||
(area['x'] * width) + (area['w'] * width),
|
(area['x'] * width) + (area['w'] * width),
|
||||||
height - (area['y'] * height) - FONT_SIZE
|
height - (area['y'] * height) - lines[..next_index].sum(&:height) + height_diff
|
||||||
],
|
],
|
||||||
A: { Type: :Action, S: :URI, URI: attachment.url }
|
A: { Type: :Action, S: :URI, URI: attachment.url }
|
||||||
})
|
}
|
||||||
end
|
)
|
||||||
when 'checkbox'
|
|
||||||
Array.wrap(value).each_with_index do |value, index|
|
|
||||||
canvas.image(PdfIcons.check_io,
|
|
||||||
at: [area['x'] * width,
|
|
||||||
height - ((area['y'] * height) + (1.2 * FONT_SIZE) - (FONT_SIZE * index))],
|
|
||||||
width: FONT_SIZE, height: FONT_SIZE)
|
|
||||||
|
|
||||||
canvas.font(FONT_NAME, size: FONT_SIZE)
|
acc + 1
|
||||||
canvas.text(value,
|
|
||||||
at: [(area['x'] * width) + FONT_SIZE,
|
|
||||||
height - ((area['y'] * height) + FONT_SIZE - (FONT_SIZE * index))])
|
|
||||||
end
|
end
|
||||||
when 'date'
|
|
||||||
canvas.font(FONT_NAME, size: FONT_SIZE)
|
layouter.fit(items, area['w'] * width, height_diff.positive? ? box_height : area['h'] * height)
|
||||||
canvas.text(I18n.l(Date.parse(value)),
|
.draw(canvas, area['x'] * width, height - (area['y'] * height) + height_diff)
|
||||||
at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)])
|
when 'checkbox'
|
||||||
|
next unless value == true
|
||||||
|
|
||||||
|
scale = [(area['w'] * width) / PdfIcons::WIDTH, (area['h'] * height) / PdfIcons::HEIGHT].min
|
||||||
|
|
||||||
|
canvas.image(
|
||||||
|
PdfIcons.check_io,
|
||||||
|
at: [
|
||||||
|
(area['x'] * width) + (area['w'] * width / 2) - (PdfIcons::WIDTH * scale / 2),
|
||||||
|
height - (area['y'] * height) - (area['h'] * height / 2) - (PdfIcons::HEIGHT * scale / 2)
|
||||||
|
],
|
||||||
|
width: PdfIcons::WIDTH * scale,
|
||||||
|
height: PdfIcons::HEIGHT * scale
|
||||||
|
)
|
||||||
else
|
else
|
||||||
canvas.font(FONT_NAME, size: FONT_SIZE)
|
value = I18n.l(Date.parse(value)) if field['type'] == 'date'
|
||||||
canvas.text(value.to_s, at: [area['x'] * width, height - ((area['y'] * height) + FONT_SIZE)])
|
|
||||||
|
text = HexaPDF::Layout::TextFragment.create(Array.wrap(value).join(', '), font: pdf.fonts.add(FONT_NAME),
|
||||||
|
font_size: FONT_SIZE)
|
||||||
|
|
||||||
|
lines = layouter.fit([text], area['w'] * width, height).lines
|
||||||
|
box_height = lines.sum(&:height)
|
||||||
|
height_diff = [0, box_height - (area['h'] * height)].max
|
||||||
|
|
||||||
|
layouter.fit([text], area['w'] * width, height_diff.positive? ? box_height : area['h'] * height)
|
||||||
|
.draw(canvas, area['x'] * width, height - (area['y'] * height) + height_diff)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -103,7 +134,6 @@ module Submissions
|
|||||||
pdf = pdfs_index[item['attachment_uuid']]
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
pdf.sign(io, reason: "Signed by #{submitter.email}",
|
pdf.sign(io, reason: "Signed by #{submitter.email}",
|
||||||
# doc_mdp_permissions: :no_changes,
|
|
||||||
certificate: OpenSSL::X509::Certificate.new(cert['cert']),
|
certificate: OpenSSL::X509::Certificate.new(cert['cert']),
|
||||||
key: OpenSSL::PKey::RSA.new(cert['key']),
|
key: OpenSSL::PKey::RSA.new(cert['key']),
|
||||||
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
|
certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']),
|
||||||
|
|||||||
Reference in New Issue
Block a user