import type * as CSS from 'csstype'; import { createSvgStr, convertForPdfLayoutProps, isEditable, addAlphaToHex, createErrorElm} from '@pdfme/schemas/utils'; import { Plugin, UIRenderProps } from '@pdfme/common'; import type { PDFRenderProps, Schema } from '@pdfme/common'; import * as Lucide from 'lucide'; import QRCode from 'qrcode'; const DEFAULT_OPACITY = 1; const HEX_COLOR_PATTERN = '^#(?:[A-Fa-f0-9]{6})$'; const DEFAULT_BARCODE_BG_COLOR = '#ffffff'; const DEFAULT_BARCODE_COLOR = '#000000'; interface QRCodeSchema extends Schema { backgroundColor: string; barColor: string; } /* uiRender */ const fullSize = { width: '100%', height: '100%' }; const createQRCodeImageElm = async (value: string) => { const qrCodeDataURL: string = await QRCode.toDataURL(value); const img: HTMLImageElement = document.createElement('img'); img.src = qrCodeDataURL; const imgStyle: CSS.Properties = { ...fullSize, borderRadius: 0 }; Object.assign(img.style, imgStyle); return img; }; // const uiRender = async (arg: UIRenderProps) => { const { value, rootElement, mode, onChange, stopEditing, tabIndex, placeholder, schema, theme } = arg; const container = document.createElement('div'); const containerStyle: CSS.Properties = { ...fullSize, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: "'Open Sans', sans-serif", }; Object.assign(container.style, containerStyle); rootElement.appendChild(container); const editable = isEditable(mode, schema); if (editable) { const input = document.createElement('input'); const inputStyle: CSS.Properties = { width: '100%', position: 'absolute', textAlign: 'center', fontSize: '12pt', fontWeight: 'bold', color: theme.colorWhite, backgroundColor: editable || value ? addAlphaToHex('#000000', 80) : 'none', border: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', overflow: 'auto', }; Object.assign(input.style, inputStyle); input.value = value; input.placeholder = placeholder || ''; input.tabIndex = tabIndex || 0; input.addEventListener('change', (e: Event) => { onChange && onChange({ key: 'content', value: (e.target as HTMLInputElement).value }); }); input.addEventListener('blur', () => { stopEditing && stopEditing(); }); container.appendChild(input); input.setSelectionRange(value.length, value.length); if (mode === 'designer') { input.focus(); } } if (!value) return; try { // if (!validateBarcodeInput(schema.type, value)) // throw new Error('[@pdfme/schemas/barcodes] Invalid barcode input'); const imgElm = await createQRCodeImageElm(value); container.appendChild(imgElm); } catch (err) { console.error(`[@pdfme/ui] ${err}`); container.appendChild(createErrorElm()); } }; /* pdfRender */ const getBarcodeCacheKey = (schema: QRCodeSchema & Schema, value: string) => { return `${schema.type}${schema.backgroundColor}${schema.barColor}${value}`; }; const pdfRender = async (arg: PDFRenderProps) => { const { value, schema, pdfDoc, page, _cache } = arg; if (!value) return; const inputBarcodeCacheKey = getBarcodeCacheKey(schema, value); let image = _cache.get(inputBarcodeCacheKey); if (!image) { const dataURL: string = await QRCode.toDataURL(value); const base64 = dataURL.split(',')[1]; if (base64) { image = await pdfDoc.embedPng(base64); _cache.set(inputBarcodeCacheKey, image); } else { console.error("Failed to generate QRCode"); } } const pageHeight = page.getHeight(); const { width, height, rotate, position: { x, y }, opacity, } = convertForPdfLayoutProps({ schema, pageHeight }); page.drawImage(image, { x, y, rotate, width, height, opacity }); }; const qrCodeSchema: Plugin = { pdf: pdfRender, ui: uiRender, propPanel: { schema: ({ i18n }: { i18n: (key: string) => string }) => ({ barColor: { title: i18n('schemas.barcodes.barColor'), type: 'string', widget: 'color', props: { disabledAlpha: true, }, rules: [ { pattern: HEX_COLOR_PATTERN, message: i18n('validation.hexColor'), }, ], }, backgroundColor: { title: i18n('schemas.bgColor'), type: 'string', widget: 'color', props: { disabledAlpha: true, }, rules: [ { pattern: HEX_COLOR_PATTERN, message: i18n('validation.hexColor'), }, ], }, }), defaultSchema: { name: '', type: 'node-qrCode', content: 'https://pdfme.com/', position: { x: 0, y: 0 }, width: 20, height: 20, backgroundColor: DEFAULT_BARCODE_BG_COLOR, barColor: DEFAULT_BARCODE_COLOR, rotate: 0, opacity: DEFAULT_OPACITY, }, }, icon: createSvgStr(Lucide.QrCode), };