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.

Revisions

  1. kyasu1 revised this gist Mar 1, 2025. No changes.
  2. kyasu1 renamed this gist Mar 1, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. kyasu1 created this gist Mar 1, 2025.
    178 changes: 178 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,178 @@
    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),
    };