Skip to content

Instantly share code, notes, and snippets.

@kyasu1
Last active March 1, 2025 09:44
Show Gist options
  • Save kyasu1/0def72d6f0826b0a9571b6e13f3c9065 to your computer and use it in GitHub Desktop.
Save kyasu1/0def72d6f0826b0a9571b6e13f3c9065 to your computer and use it in GitHub Desktop.
A light weight qrcode plugin for pdfme
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<QRCodeSchema>) => {
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<QRCodeSchema>) => {
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<QRCodeSchema> = {
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),
};
@kyasu1
Copy link
Author

kyasu1 commented Mar 1, 2025

Before using this plugin, please install qrcode package from npm.

# npm install qrcode -S

Then put this file under your projfect folder somewhere like `./src/plugins/qrCode.ts', then import it from your generator / playgournd file as follows.

import qrCode from "./plugins/qrCode.js';

async funtionc generatePdf(template: Template, inputs: any) {
    const pdf = await.generate({
        template, inputs, options: { font },
        plugins: { Table: table, multiVariableText, text, line, rectangle, **NodeQRCode: qrCode**}
    });

Add the following schema into your template.

            {
                "type": "node-qrCode",
                "content": "https://pdfme.com/",
                "position": {
                    "x": 178,
                    "y": 20
                },
                "backgroundColor": "#ffffff",
                "barColor": "#000000",
                "width": 16,
                "height": 16,
                "rotate": 0,
                "opacity": 1,
                "required": false,
                "readOnly": false,
                "name": "qrCode"
            },

@kyasu1
Copy link
Author

kyasu1 commented Mar 1, 2025

You can see the difference in the bundle size as follow:

bwip-js

Screenshot 2025-02-20 at 22 27 15

qrcode only

Screenshot 2025-02-20 at 23 20 09

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment