Data

PdfZoneEditor

Editor visuale delle zone (firma, iniziali, testo, data) sulle pagine di un PDF reso con pdf.js. Zone in percentuale della pagina, trascinabili, ridimensionabili e gestibili da tastiera.

Import

import { PdfZoneEditor } from '@pzeta/vue-components'

I tipi (PdfZone, PdfZoneDropEvent, PdfZoneOwner, PdfZoneField…) si importano dalla radice del pacchetto dalla 1.24.1: nella 1.24.0 non erano esportati.

Prerequisiti: pdf.js

Il PDF è reso con pdf.js. pdfjs-dist è una peer dependency opzionale della libreria, come chart.js e qrcode: si installa solo dove si usa l'editor, e viene importato al primo documento aperto, quindi chi non usa il componente non lo scarica.

npm install pdfjs-dist   # versione 5 o successiva

pdf.js lavora in un worker, che va indicato in uno dei due modi:

// Una volta per tutta l'applicazione
import { GlobalWorkerOptions } from 'pdfjs-dist'
import workerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
GlobalWorkerOptions.workerSrc = workerSrc
<!-- Oppure per singolo editor -->
<PdfZoneEditor :src="pdf" :worker-src="workerSrc" />

Senza worker l'editor non apre il documento e mostra al suo posto «Manca il worker di pdf.js: imposta workerSrc», emettendo error.

Esempio Base

Per l'anteprima servono pdfjs-dist e il worker, che questo sito non carica: l'esempio è solo codice. Una demo funzionante è nella showcase della libreria (/data/pdf-zone-editor).

<script setup lang="ts">
import { ref } from 'vue'
import workerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
import { PdfZoneEditor, type PdfZone, type PdfZoneDropEvent } from '@pzeta/vue-components'

const props = defineProps<{ pdf: Blob }>()
const pagina = ref(1)
const attiva = ref<string | null>(null)
const zone = ref<PdfZone[]>([
  { key: 'firma-1', page: 1, x: 9, y: 80, width: 32, height: 7, type: 'firma', label: 'Firma', color: 0, required: true },
])

// La zona la crea il chiamante: chiave, proprietario e dimensioni sono suoi.
function aggiungi({ field, page, x, y }: PdfZoneDropEvent) {
  const key = crypto.randomUUID()
  zone.value = [
    ...zone.value,
    { key, page, x: Math.min(x, 75), y: Math.min(y, 93), width: 25, height: 7, type: field.type, label: field.label, color: 0 },
  ]
  attiva.value = key
}
</script>

<template>
  <PdfZoneEditor
    v-model:zones="zone"
    v-model:page="pagina"
    :src="props.pdf"
    :worker-src="workerSrc"
    :owners="[{ id: 1, name: 'Mario Rossi', colorIndex: 0 }, { id: 2, name: 'Anna Verdi', colorIndex: 1 }]"
    :field-palette="[
      { type: 'firma', label: 'Firma' },
      { type: 'iniziali', label: 'Iniziali' },
      { type: 'data', label: 'Data' },
    ]"
    :active-zone="attiva"
    @select="attiva = $event"
    @drop="aggiungi"
  />
</template>

Coordinate

x, y, width e height di una zona sono percentuali della pagina (0–100), con l'origine in alto a sinistra. Restano valide a qualunque scala: la pagina viene ridisegnata quando cambia la larghezza del contenitore, e le zone la seguono. Le zone sono sempre tenute dentro la pagina e non scendono sotto minZoneSize.

Props

PropTipoDefaultDescrizione
srcBlob | ArrayBuffer | Uint8Array | string | null—Il PDF: Blob/File, dati binari o URL
zonesPdfZone[][]Zone di tutte le pagine (v-model:zones)
ownersPdfZoneOwner[][]Proprietari delle zone, per la legenda dei colori
fieldPalettePdfZoneField[][]Campi trascinabili sulla pagina; senza voci la palette non compare
activeZonestring | nullnullZona evidenziata; se sta su un'altra pagina l'editor ci si porta
pagenumber1Pagina mostrata, da 1 (v-model:page)
readonlybooleanfalseSola lettura: le zone si selezionano ma non si spostano, niente palette
workerSrcstring—URL del worker di pdf.js; in alternativa GlobalWorkerOptions.workerSrc
minZoneSizenumber2Dimensione minima di una zona, in percentuale
ariaLabelstring«Documento PDF»Nome accessibile dell'editor

PdfZone

interface PdfZone {
  key: string        // identificativo univoco
  page: number       // pagina, da 1
  x: number          // bordo sinistro, % della larghezza
  y: number          // bordo superiore, % dell'altezza
  width: number      // % della larghezza
  height: number     // % dell'altezza
  type: string       // tipo di campo (firma, iniziali, testo, data…), libero
  label: string      // etichetta mostrata e letta dagli screen reader
  color?: number     // indice del colore (di solito quello del proprietario)
  required?: boolean // asterisco e lettura «obbligatorio»
  filled?: boolean   // spunta e lettura «compilato»
}

PdfZoneOwner e PdfZoneField

interface PdfZoneOwner {
  id: number | string
  name: string
  colorIndex: number // indice del colore delle sue zone
}

interface PdfZoneField {
  type: string
  label: string
}

color e colorIndex scelgono una tinta fra le palette semantiche della libreria (primary, success, warn, danger, info, help, secondary, contrast, poi da capo): bordo -500 e sfondo trasparente della stessa tinta, più pieno per le zone compilate.

Emits

EventoPayloadDescrizione
update:zonesPdfZone[]Zone dopo uno spostamento, un ridimensionamento o un'eliminazione
update:pagenumberPagina dopo la navigazione
selectstringChiave della zona cliccata o attivata da tastiera
dropPdfZoneDropEventUn campo della palette rilasciato sulla pagina (o premuto)
load{ pages: number }Documento aperto, col numero di pagine
errorErrorErrore di apertura o di resa del PDF
interface PdfZoneDropEvent {
  field: PdfZoneField
  page: number
  x: number // punto di rilascio, % della pagina
  y: number
}

Un campo trascinato dalla palette dà il punto di rilascio; premuto (col mouse o da tastiera) va al centro della pagina corrente (x: 50, y: 50). L'editor non crea la zona: la crea il chiamante, che ne decide chiave, proprietario e dimensioni.

Slot

SlotScopeDescrizione
toolbar{ page: number; pages: number; goToPage: (page: number) => void }Sostituisce navigazione e legenda
palette{ fields: PdfZoneField[]; addField: (field: PdfZoneField) => void }Sostituisce la palette dei campi
zone{ zone: PdfZone; active: boolean }Contenuto di ogni zona (default: etichetta, spunta, asterisco)

Interazione

  • Puntatore: la zona si sposta trascinandola e si ridimensiona dalla maniglia in basso a destra; un clic senza movimento la seleziona soltanto. Sul tocco la pagina non scorre mentre si trascina una zona.
  • Tastiera (con la zona a fuoco): frecce spostano di 1% (10% con Ctrl), Maiusc+frecce ridimensionano, Canc/Backspace elimina, Invio/Spazio seleziona.
  • Palette: campi trascinabili sulla pagina (drag & drop HTML5) o premibili.

Accessibilità

  • Ogni zona è un pulsante raggiungibile col Tab, con nome «Firma, pagina 1, obbligatorio» (o «compilato») e aria-pressed sulla zona attiva; le istruzioni da tastiera sono collegate con aria-describedby.
  • Spostamenti, ridimensionamenti ed eliminazioni sono annunciati in una live region («Firma: posizione 20%, 30%»).
  • I pulsanti della palette si chiamano «Aggiungi Firma»; l'indicatore «Pagina 1 di 2» è una live region. Il canvas della pagina è decorativo (aria-hidden).
  • Stringhe nella sezione i18n pdfZoneEditor dei cinque dizionari.

TypeScript

import type {
  PdfZoneEditorProps,
  PdfZoneEditorEmits,
  PdfZone,
  PdfZoneOwner,
  PdfZoneField,
  PdfZoneDropEvent,
} from '@pzeta/vue-components'