FreeReportv0.12.0한국어

Printed-document verification

A QR code on the printout opens your verify page, which re-renders the stored original and compares SHA-256 fingerprints. Data stays on your servers.

Someone holding a printout scans its QR code and sees whether it is genuine and unchanged, like the verifiers of other reporting tools, except that the document never leaves your servers. When you issue a document you keep its definition and data (or a snapshot of both) under a document id in your own database. The QR code holds your verify address with that id and a check code; your verify page loads the stored original, lays it out again and compares the fingerprints. FreeReport never receives the document. Try it with the certificate demo

// 1. issuing: the QR holds your verify address, the id and the check code
const definition = {
  name: '재직증명서',
  verifyUrl: 'https://erp.example.com/verify?id={params.docId}&h={docHashShort}',
  params: { docId: { type: 'string' } },
  bands: { title: { height: 250, elements: [
    // …the certificate…
    { type: 'barcode', format: 'qr', x: 0, y: 226, w: 22, h: 22, expr: 'verifyUrl' },
    { type: 'text', x: 24, y: 232, w: 90, h: 8, text: '확인코드 {docHashShort}' },
  ] } },
};
const report = createReport(definition, data, { params: { docId } });
await report.ready();
// keep what you printed, on your own server
await db.insert('issued_documents', { id: docId, definition, data, params: { docId }, hash: report.fingerprint(), issuedAt: new Date() });
// 2. https://erp.example.com/verify?id=…&h=… (your page)
import { createVerifierPage } from '@freereportjs/core';
import '@freereportjs/core/styles.css';

const q = new URLSearchParams(location.search);
createVerifierPage(document.getElementById('verify')!, {
  id: q.get('id')!,
  hash: q.get('h')!,
  lang: 'ko',
  load: async id => {
    const r = await fetch('/api/issued-documents/' + encodeURIComponent(id));
    return r.ok ? r.json() : null;          // { definition, data, params, issuer, issuedAt }
  },
});
NameTypeDescription
verifyUrldefinition: stringURL template for the QR code; {expressions} see data, params, docHash and docHashShort and are URL-encoded. Print it with a QR whose expr is verifyUrl.
verifyReport(definition, data, hash, opts?)Promise<{ ok, hash, expected, pages }>Lays the report out again (waiting for web fonts in browsers) and compares its fingerprint with the hash: the full 64 hex or a prefix of at least 8 (docHashShort). opts are layout options (params, measure, locale).
createVerifierPage(el, { load, id, hash, lang? })VerifierPageReady-made verify screen: calls load(id) on your server, shows 원본과 일치 / Matches the original or 일치하지 않음 / Does not match with the issuer and issue time, and the original pages read-only under a 검증용 사본 / Verification copy watermark (no print or save buttons). done resolves with the result; destroy() removes it.
load(id){ definition, data?, params?, issuedAt?, issuer? } | nullYour function: return the definition and data stored for the id (null when there is none).
verifyReportServer(definition, data, hash, { fonts })Promise<VerifyResult>The same check in Node.js, Deno or Bun, measuring text with TrueType fonts (as renderPdf does).

What the check code covers

The fingerprint is a SHA-256 of the text printed on every page (text and field elements, page by page in reading order, whitespace collapsed). Watermarks, barcodes and QR codes, images, lines, boxes, charts, emboss marks and any text that mentions docHash or verifyUrl are left out, so printing the hash or the verify QR never changes the hash. docHashShort is its first 12 hex digits. Line and page breaks count: verify with the same fonts the document was issued with (the browser viewer with the built-in web fonts, or verifyReportServer with the same TTF files), and print dates from your data rather than now().

// or check on the server (Node.js, Deno, Bun)
import { verifyReportServer } from '@freereportjs/core';

const doc = await db.get('issued_documents', id);
const { ok, hash } = await verifyReportServer(doc.definition, doc.data, h, { params: doc.params, fonts });

The verify page shows the original with a "검증용 사본 / Verification copy" watermark in place of the document's own watermarks and without print or save buttons, so the copy cannot pass as an original. Keep the stored record for as long as the document must stay verifiable; the check code alone proves nothing without it.