@tato30/vue-pdf
Version:
PDF component for Vue 3
242 lines (224 loc) • 7.66 kB
text/typescript
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable no-case-declarations */
import type { PDFDocumentProxy } from 'pdfjs-dist'
import type { RefProxy } from 'pdfjs-dist/types/src/display/api'
import type { AnnotationEventPayload } from '../types'
interface PopupArgs {
[key: string]: string
}
interface LinkAnnotation {
dest: Array<any> | string
url: string
unsafeurl: string
}
const INTERNAL_LINK = 'internal-link'
const LINK = 'link'
const FILE_ATTACHMENT = 'file-attachment'
const FORM_TEXT = 'form-text'
const FORM_SELECT = 'form-select'
const FORM_CHECKBOX = 'form-checkbox'
const FORM_RADIO = 'form-radio'
const FORM_BUTTON = 'form-button'
const EVENTS_TO_HANDLER = ['click', 'dblclick', 'mouseover', 'input', 'change']
function getAnnotationsByKey(key: string, value: any, annotations: Object[]): any[] {
const result = []
if (annotations) {
for (const annotation of annotations) {
type Key = keyof typeof annotation
if (annotation[key as Key] === value)
result.push(annotation)
}
}
return result
}
function buildAnnotationData(type: string, data: any): AnnotationEventPayload {
return { type, data }
}
function inputAnnotation(inputEl: any, args?: any) {
switch (inputEl.type) {
case 'textarea':
case 'text':
return buildAnnotationData(FORM_TEXT, {
fieldName: inputEl.name,
value: inputEl.value,
})
case 'select-one':
case 'select-multiple':
const options = []
for (const opt of inputEl.options) {
options.push({
value: opt.value,
label: opt.label,
})
}
const selected = []
for (const opt of inputEl.selectedOptions) {
selected.push({
value: opt.value,
label: opt.label,
})
}
return buildAnnotationData(FORM_SELECT, {
fieldName: inputEl.name,
value: selected,
options,
})
case 'checkbox':
return buildAnnotationData(FORM_CHECKBOX, {
fieldName: inputEl.name,
checked: inputEl.checked,
})
case 'radio':
return buildAnnotationData(FORM_RADIO, {
fieldName: inputEl.name,
...args,
})
case 'button':
return buildAnnotationData(FORM_BUTTON, {
fieldName: inputEl.name,
...args,
})
}
}
function fileAnnotation(annotation: any) {
return buildAnnotationData(FILE_ATTACHMENT, annotation.file)
}
async function linkAnnotation(annotation: {
dest?: any
url?: string
unsafeUrl?: string
}, PDFDoc: PDFDocumentProxy) {
if (annotation.dest) {
let explicitDest
if (typeof annotation.dest === 'string')
explicitDest = await PDFDoc.getDestination(annotation.dest)
else
explicitDest = annotation.dest
if (!Array.isArray(explicitDest)) {
console.warn(`Destination "${explicitDest}" is not a valid destination (dest="${annotation.dest}")`)
return buildAnnotationData(INTERNAL_LINK, {
referencedPage: null,
offset: null,
})
}
let offset = null
if (explicitDest.length === 5) {
offset = {
left: annotation.dest[2],
bottom: annotation.dest[3],
}
}
const [destRef] = explicitDest
if (Number.isInteger(destRef)) {
return buildAnnotationData(INTERNAL_LINK, {
referencedPage: Number(destRef) + 1,
offset,
})
}
else if (typeof destRef === 'object') {
const pageNumber = await PDFDoc.getPageIndex(destRef as RefProxy)
return buildAnnotationData(INTERNAL_LINK, {
referencedPage: pageNumber + 1,
offset,
})
}
else {
console.warn(
`Destination "${destRef}" is not a valid destination (dest="${annotation.dest}")`,
)
return buildAnnotationData(INTERNAL_LINK, {
referencedPage: null,
offset: null,
})
}
}
else if (annotation.url) {
return buildAnnotationData(LINK, {
url: annotation.url,
unsafeUrl: annotation.unsafeUrl,
})
}
}
function mergePopupArgs(annotation: HTMLElement) {
for (const spanElement of annotation.getElementsByTagName('span')) {
let content = spanElement.textContent
const args = JSON.parse(spanElement.dataset.l10nArgs ?? '{}') as PopupArgs
if (content) {
for (const key in args)
content = content.replace(`{{${key}}}`, args[key])
}
spanElement.textContent = content
}
}
// Use this function to handle annotation events
function annotationEventsHandler(evt: Event, PDFDoc: PDFDocumentProxy, Annotations: Object[]) {
let annotation = evt.target as HTMLElement
// annotations are <section> elements if target element are not <section> the parentNode should be
if (annotation.tagName !== 'SECTION')
annotation = annotation.parentNode! as HTMLElement
if (annotation.className === 'linkAnnotation' && evt.type === 'click') {
const id: string | undefined = annotation.dataset?.annotationId
if (id)
return linkAnnotation(getAnnotationsByKey('id', id, Annotations)[0] as LinkAnnotation, PDFDoc)
}
else if (annotation.className.includes('popupAnnotation') || annotation.className.includes('textAnnotation')) {
mergePopupArgs(annotation)
}
else if (annotation.className.includes('fileAttachmentAnnotation')) {
mergePopupArgs(annotation)
const id = annotation.dataset.annotationId
if (id && evt.type === 'dblclick')
return fileAnnotation(getAnnotationsByKey('id', id, Annotations)[0])
}
else if (annotation.className.includes('textWidgetAnnotation') && evt.type === 'input') {
let inputElement: HTMLInputElement | HTMLTextAreaElement = annotation.getElementsByTagName('input')[0]
if (!inputElement)
inputElement = annotation.getElementsByTagName('textarea')[0]
return inputAnnotation(inputElement)
}
else if (annotation.className.includes('choiceWidgetAnnotation') && evt.type === 'input') {
return inputAnnotation(annotation.getElementsByTagName('select')[0])
}
else if (annotation.className.includes('buttonWidgetAnnotation checkBox') && evt.type === 'change') {
return inputAnnotation(annotation.getElementsByTagName('input')[0])
}
else if (annotation.className.includes('buttonWidgetAnnotation radioButton') && evt.type === 'change') {
const id = annotation.dataset.annotationId
if (id) {
const anno = getAnnotationsByKey('id', id, Annotations)[0]
const radioOptions = []
for (const radioAnnotations of getAnnotationsByKey('fieldName', anno.fieldName, Annotations)) {
if (radioAnnotations.buttonValue)
radioOptions.push(radioAnnotations.buttonValue)
}
return inputAnnotation(annotation.getElementsByTagName('input')[0], {
value: anno.buttonValue,
defaultValue: anno.fieldValue,
options: radioOptions,
})
}
}
else if (annotation.className.includes('buttonWidgetAnnotation pushButton') && evt.type === 'click') {
const id = annotation.dataset.annotationId
if (id) {
const anno = getAnnotationsByKey('id', id, Annotations)[0]
if (!anno.resetForm) {
return inputAnnotation(
{ name: anno.fieldName, type: 'button' },
{ actions: anno.actions, reset: false },
)
}
else {
return inputAnnotation(
{ name: anno.fieldName, type: 'button' },
{ actions: anno.actions, reset: true },
)
}
}
}
}
export {
annotationEventsHandler, EVENTS_TO_HANDLER
}