UNPKG

@pdfme/schemas

Version:

TypeScript base PDF generator and React base UI. Open source, developed by the community, and completely free to use under the MIT license!

1,504 lines 185 kB
import { $ as VERTICAL_ALIGN_MIDDLE, F as DEFAULT_FONT_COLOR, G as PLACEHOLDER_FONT_COLOR, K as SYNTHETIC_BOLD_CSS_TEXT_SHADOW, N as DEFAULT_ALIGNMENT, a as createListItemSplitRange, b as createBoxDimension, c as getListItemRange, h as getFontKitFont, i as TEXT_LINE_SPLIT_UNIT, j as CODE_BACKGROUND_COLOR, l as getTableBodyRange, n as LIST_ITEM_SPLIT_UNIT, o as createTableBodySplitRange, r as TABLE_BODY_SPLIT_UNIT, s as createTextLineSplitRange, t as BUILT_IN_DYNAMIC_LAYOUT_SPLIT_UNITS, u as getTextLineRange, x as getBoxContentArea } from "./splitRange-DmVDtmzO.js"; import { a as measureTextLines, d as isInlineMarkdownTextSchema, g as parseInlineMarkdown, p as resolveFontVariant } from "./measure-L5diay3k.js"; import { a as mapVerticalAlignToFlex, c as Formatter, i as makeElementPlainTextContentEditable, l as getExtraFormatterSchema, n as textSchema, o as uiRender$4, r as buildStyledTextContainer, s as propPanel$3, t as builtInPlugins, u as pdfRender$4 } from "./builtins-BB2DHceW.js"; import { a as getCellPropPanelSchema, c as HEX_COLOR_PATTERN, i as getBodyWithSchemaRange, l as createSingleTable, n as getDynamicLayoutForTable, o as getColumnStylesPropPanelSchema, r as getBody, s as getDefaultCellStyles, t as getDynamicHeightsForTable } from "./dynamicTemplate-B4GCNLF9.js"; import { c as isEditable, d as countUniqueVariableNames, f as getVariableNames, i as createSvgStr, l as readFile, n as convertForPdfLayoutProps, o as hex2PrintingColor, p as visitVariables, r as createErrorElm, t as addAlphaToHex, u as rotatePoint } from "./utils-zDZkqBnX.js"; import { a as normalizeListItems, c as LIST_STYLE_BULLET, i as normalizeListItemEntries, l as LIST_STYLE_ORDERED, n as calculateListLayout, o as serializeListItems, s as DEFAULT_LIST_STYLE, t as getDynamicLayoutForList } from "./dynamicTemplate-BwzF9C1L.js"; import { n as substituteVariablesAsInlineMarkdownLiterals, r as validateVariables, t as substituteVariables } from "./helper-CEme39Uo.js"; import "./tables.js"; import "./lists.js"; import { DEFAULT_FONT_NAME, ZOOM, b64toUint8Array, getDefaultFont, getFallbackFontName, getInternalLinkTarget, mm2pt, normalizeLinkHref, px2mm } from "@pdfme/common"; import { Buffer as Buffer$1 } from "buffer"; import { toRadians } from "@pdfme/pdf-lib"; import { Barcode, Calendar, CalendarClock, ChevronDown, Circle, CircleDot, Clock, Image, List, Minus, QrCode, Route, Square, SquareCheck, Table, Type } from "lucide"; import DOMPurify from "dompurify"; import bwipjs from "bwip-js"; import AirDatepicker from "air-datepicker"; import * as dateFns from "date-fns/locale"; import { format } from "date-fns"; //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports); var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion //#region src/multiVariableText/pdfRender.ts var pdfRender$3 = async (arg) => { const { value, schema, ...rest } = arg; if (schema.readOnly) { await pdfRender$4({ value, schema, ...rest }); return; } if (!validateVariables(value, schema)) return; await pdfRender$4({ value: isInlineMarkdownTextSchema(schema) ? substituteVariablesAsInlineMarkdownLiterals(schema.text || "", value) : substituteVariables(schema.text || "", value), schema, ...rest }); }; //#endregion //#region src/multiVariableText/propPanel.ts var mapDynamicVariables = (props) => { const { rootElement, changeSchemas, activeSchema, i18n, options } = props; const mvtSchema = activeSchema; const text = mvtSchema.text || ""; let variables = {}; try { const parsed = JSON.parse(mvtSchema.content || "{}"); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) variables = parsed; } catch {} const variablesChanged = updateVariablesFromText(text, variables); const varNames = Object.keys(variables); if (variablesChanged) changeSchemas([ { key: "content", value: JSON.stringify(variables), schemaId: activeSchema.id }, { key: "variables", value: varNames, schemaId: activeSchema.id }, { key: "readOnly", value: varNames.length === 0, schemaId: activeSchema.id } ]); const placeholderRowEl = document.getElementById("placeholder-dynamic-var")?.closest(".ant-form-item"); if (!placeholderRowEl) throw new Error("Failed to find Ant form placeholder row to create dynamic variables inputs."); placeholderRowEl.style.display = "none"; rootElement.parentElement.style.display = "block"; if (varNames.length > 0) for (let variableName of varNames) { const varRow = placeholderRowEl.cloneNode(true); const textarea = varRow.querySelector("textarea"); textarea.id = "dynamic-var-" + variableName; textarea.value = variables[variableName]; textarea.addEventListener("change", (e) => { if (variableName in variables) { variables[variableName] = e.target.value; changeSchemas([{ key: "content", value: JSON.stringify(variables), schemaId: activeSchema.id }]); } }); const label = varRow.querySelector("label"); label.innerText = variableName; varRow.style.display = "block"; rootElement.appendChild(varRow); } else { const para = document.createElement("p"); const colorValue = options?.theme?.token?.colorPrimary || "#168fe3"; const safeColorValue = /^#[0-9A-F]{6}$/i.test(colorValue) || /^(rgb|hsl)a?\(\s*([+-]?\d+%?\s*,\s*){2,3}[+-]?\d+%?\s*\)$/i.test(colorValue) ? colorValue : "#168fe3"; const typingInstructions = i18n("schemas.mvt.typingInstructions"); const sampleField = i18n("schemas.mvt.sampleField"); para.appendChild(document.createTextNode(typingInstructions + " ")); const codeEl = document.createElement("code"); codeEl.style.color = safeColorValue; codeEl.style.fontWeight = "bold"; codeEl.textContent = `{${sampleField}}`; para.appendChild(codeEl); rootElement.appendChild(para); } }; var propPanel$2 = { schema: (propPanelProps) => { if (typeof propPanel$3.schema !== "function") throw new Error("Oops, is text schema no longer a function?"); const parentSchema = typeof propPanel$3.schema === "function" ? propPanel$3.schema(propPanelProps) : {}; const i18n = propPanelProps.i18n; return { ...parentSchema, "-------": { type: "void", widget: "Divider" }, dynamicVarContainer: { title: i18n("schemas.mvt.variablesSampleData"), type: "string", widget: "Card", span: 24, properties: { dynamicVariables: { type: "object", widget: "mapDynamicVariables", bind: false, span: 24 }, placeholderDynamicVar: { title: i18n("schemas.mvt.placeholderDynamicVariable"), type: "string", format: "textarea", props: { id: "placeholder-dynamic-var", autoSize: { minRows: 2, maxRows: 5 } }, span: 24 } } } }; }, widgets: { ...propPanel$3.widgets, mapDynamicVariables }, defaultSchema: { ...propPanel$3.defaultSchema, readOnly: false, type: "multiVariableText", text: "Add text here using {} for variables ", width: 50, height: 15, content: "{}", variables: [] } }; var updateVariablesFromText = (text, variables) => { const matches = getVariableNames(text); let changed = false; if (matches.length > 0) { const uniqueMatches = new Set(matches); for (const variableName of uniqueMatches) if (!(variableName in variables)) { variables[variableName] = variableName.toUpperCase(); changed = true; } Object.keys(variables).forEach((variableName) => { if (!uniqueMatches.has(variableName)) { delete variables[variableName]; changed = true; } }); } else Object.keys(variables).forEach((variableName) => { delete variables[variableName]; changed = true; }); return changed; }; //#endregion //#region src/multiVariableText/uiRender.ts var uiRender$3 = async (arg) => { const { value, schema, rootElement, mode, onChange, ...rest } = arg; let text = schema.text; let numVariables = schema.variables.length; const renderResolvedValue = schema.readOnly === true && mode !== "designer"; const renderValue = renderResolvedValue ? value : isInlineMarkdownTextSchema(schema) ? substituteVariablesAsInlineMarkdownLiterals(text, value) : substituteVariables(text, value); if (mode === "form" && numVariables > 0 && !renderResolvedValue) { await formUiRender(arg); return; } await uiRender$4({ value: isEditable(mode, schema) ? text : renderValue, schema, mode: mode === "form" ? "viewer" : mode, rootElement, onChange: (arg) => { if (!Array.isArray(arg)) { if (onChange) onChange({ key: "text", value: arg.value }); } else throw new Error("onChange is not an array, the parent text plugin has changed..."); }, ...rest }); const textBlock = rootElement.querySelector("#text-" + String(schema.id)); if (!textBlock) throw new Error("Text block not found. Ensure the text block has an id of \"text-\" + schema.id"); if (mode === "designer") textBlock.addEventListener("keyup", (event) => { text = textBlock.textContent || ""; if (keyPressShouldBeChecked(event)) { const newNumVariables = countUniqueVariableNames(text); if (numVariables !== newNumVariables) { if (onChange) onChange({ key: "text", value: text }); numVariables = newNumVariables; } } }); }; var formUiRender = async (arg) => { const { value, schema, rootElement, onChange, stopEditing, theme, _cache, options } = arg; const rawText = schema.text; if (rootElement.parentElement) rootElement.parentElement.style.outline = ""; let variables = {}; if (value) try { const parsed = JSON.parse(value); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) variables = parsed; } catch {} const substitutedText = substituteVariables(rawText, variables); const inlineMarkdownRuns = isInlineMarkdownTextSchema(schema) ? parseInlineMarkdown(rawText) : void 0; const font = options?.font || getDefaultFont(); const textBlock = buildStyledTextContainer(arg, await getFontKitFont(schema.fontName, font, _cache), inlineMarkdownRuns ? getInlineMarkdownFormDisplayText(inlineMarkdownRuns, variables) : substitutedText); if (getTextLineRange(schema)) { const { lines } = await measureTextLines({ value: inlineMarkdownRuns ? substituteVariablesAsInlineMarkdownLiterals(rawText, variables) : substitutedText, schema, font, _cache, ignoreDynamicFontSize: true }); renderSplitVariableSpans({ textBlock, lines, runs: inlineMarkdownRuns, rawText, variables, schema, font, theme, onChange, stopEditing }); return; } if (inlineMarkdownRuns) { renderInlineMarkdownVariableSpans({ runs: inlineMarkdownRuns, variables, textBlock, schema, font, theme, onChange, stopEditing }); return; } const variableIndices = /* @__PURE__ */ new Map(); visitVariables(rawText, ({ name, startIndex }) => { variableIndices.set(startIndex, name); }); let inVarString = false; for (let i = 0; i < rawText.length; i++) { const variableName = variableIndices.get(i); if (variableName) { inVarString = true; let span = document.createElement("span"); span.style.outline = `${theme.colorPrimary} dashed 1px`; makeElementPlainTextContentEditable(span); span.textContent = variables[variableName]; span.addEventListener("blur", (e) => { const newValue = e.target.textContent || ""; if (newValue !== variables[variableName]) { variables[variableName] = newValue; if (onChange) onChange({ key: "content", value: JSON.stringify(variables) }); if (stopEditing) stopEditing(); } }); textBlock.appendChild(span); } else if (inVarString) { if (rawText[i] === "}") inVarString = false; } else { let span = document.createElement("span"); span.style.letterSpacing = rawText.length === i + 1 ? "0" : "inherit"; span.textContent = rawText[i]; textBlock.appendChild(span); } } }; var renderSplitVariableSpans = (arg) => { const { textBlock, lines, runs, rawText, variables, schema, font, theme, onChange, stopEditing } = arg; const lineRange = getTextLineRange(schema); const lineSegments = getSplitLineSegments({ lines, runs, rawText, variables, start: lineRange?.start ?? 0, end: lineRange?.end ?? lines.length }); textBlock.innerHTML = ""; lineSegments.forEach((segments, lineIndex) => { segments.forEach((segment) => { if (segment.variableName) { appendRangedVariableSpan({ textBlock, segment, variables, schema, font, theme, onChange, stopEditing }); return; } const span = segment.run ? createStaticInlineMarkdownElement(segment.run) : document.createElement("span"); span.style.letterSpacing = lineIndex === lineSegments.length - 1 ? "0" : "inherit"; span.textContent = segment.text; if (segment.run) applyInlineMarkdownStyle({ element: span, run: segment.run, schema, font }); textBlock.appendChild(span); }); if (lineIndex < lineSegments.length - 1) textBlock.appendChild(document.createElement("br")); }); }; var getSplitLineSegments = (arg) => { const { lines, runs, rawText, variables, start, end } = arg; return consumeMeasuredLineSegments(lines, runs ? buildResolvedInlineMarkdownChars(runs, variables) : buildResolvedPlainChars(rawText, variables), { dropUnmappedTargets: Boolean(runs) }).slice(start, end); }; var buildResolvedPlainChars = (rawText, variables) => { const chars = []; let lastIndex = 0; visitVariables(rawText, ({ name, startIndex, endIndex }) => { appendTextChars(chars, rawText.slice(lastIndex, startIndex)); const value = variables[name] ?? ""; for (let i = 0; i < value.length; i += 1) chars.push({ char: value[i], variableName: name, variableOffset: i }); lastIndex = endIndex + 1; }); appendTextChars(chars, rawText.slice(lastIndex)); return chars; }; var buildResolvedInlineMarkdownChars = (runs, variables) => { const chars = []; runs.forEach((run) => { let lastIndex = 0; visitVariables(run.text, ({ name, startIndex, endIndex }) => { appendTextChars(chars, run.text.slice(lastIndex, startIndex), run); const value = variables[name] ?? ""; for (let i = 0; i < value.length; i += 1) chars.push({ char: value[i], variableName: name, variableOffset: i, run }); lastIndex = endIndex + 1; }); appendTextChars(chars, run.text.slice(lastIndex), run); }); return chars; }; var appendTextChars = (chars, text, run) => { for (let i = 0; i < text.length; i += 1) chars.push({ char: text[i], run }); }; var consumeMeasuredLineSegments = (lines, resolvedChars, options = {}) => { const lineSegments = []; let cursor = 0; lines.forEach((line) => { const segments = []; const lineText = stripTrailingLineBreaks(line); for (let i = 0; i < lineText.length; i += 1) { const target = lineText[i]; while (cursor < resolvedChars.length && resolvedChars[cursor].char !== target && isWhitespaceChar(resolvedChars[cursor].char) && !isWhitespaceChar(target)) cursor += 1; if (cursor >= resolvedChars.length) { if (options.dropUnmappedTargets) continue; appendSegment(segments, { char: target }); continue; } const sourceChar = resolvedChars[cursor]; if (sourceChar.char === target) { appendSegment(segments, sourceChar); cursor += 1; } else { if (options.dropUnmappedTargets) continue; appendSegment(segments, { char: target }); } } cursor = absorbHiddenTrailingWhitespace(segments, resolvedChars, cursor); if (line.endsWith("\r\n") || line.endsWith("\n") || line.endsWith("\r")) { if (resolvedChars[cursor]?.char === "\r" && resolvedChars[cursor + 1]?.char === "\n") cursor += 2; else if (resolvedChars[cursor]?.char === "\n" || resolvedChars[cursor]?.char === "\r") cursor += 1; } lineSegments.push(segments); }); return lineSegments; }; var absorbHiddenTrailingWhitespace = (segments, resolvedChars, cursor) => { let nextCursor = cursor; while (nextCursor < resolvedChars.length && isHorizontalWhitespaceChar(resolvedChars[nextCursor].char)) { const sourceChar = resolvedChars[nextCursor]; const lastSegment = segments.at(-1); if (lastSegment && lastSegment.variableName === sourceChar.variableName && lastSegment.variableEnd === sourceChar.variableOffset && lastSegment.run === sourceChar.run && sourceChar.variableOffset !== void 0) lastSegment.variableEnd = sourceChar.variableOffset + 1; nextCursor += 1; } return nextCursor; }; var stripTrailingLineBreaks = (value) => { let end = value.length; while (end > 0) { const char = value[end - 1]; if (char !== "\n" && char !== "\r") break; end -= 1; } return value.slice(0, end); }; var isWhitespaceChar = (value) => value === " " || value === " " || value === "\n" || value === "\r" || value === "\f" || value === "\v"; var isHorizontalWhitespaceChar = (value) => value === " " || value === " " || value === "\f" || value === "\v"; var appendSegment = (segments, sourceChar) => { const lastSegment = segments.at(-1); if (lastSegment && lastSegment.variableName === sourceChar.variableName && lastSegment.variableEnd === sourceChar.variableOffset && lastSegment.run === sourceChar.run) { lastSegment.text += sourceChar.char; if (sourceChar.variableOffset !== void 0) lastSegment.variableEnd = sourceChar.variableOffset + 1; return; } segments.push({ text: sourceChar.char, variableName: sourceChar.variableName, variableStart: sourceChar.variableOffset, variableEnd: sourceChar.variableOffset === void 0 ? void 0 : sourceChar.variableOffset + 1, run: sourceChar.run }); }; var appendRangedVariableSpan = (arg) => { const { textBlock, segment, variables, schema, font, theme, onChange, stopEditing } = arg; if (!segment.variableName) return; const span = document.createElement("span"); span.style.outline = `${theme.colorPrimary} dashed 1px`; if (segment.run) applyInlineMarkdownStyle({ element: span, run: segment.run, schema, font }); makeElementPlainTextContentEditable(span); span.textContent = segment.text; span.addEventListener("blur", (e) => { const variableName = segment.variableName; if (!variableName) return; const newValue = e.target.textContent || ""; if (newValue === segment.text) return; const currentValue = variables[variableName] ?? ""; const start = Math.min(segment.variableStart ?? 0, currentValue.length); const end = Math.min(segment.variableEnd ?? currentValue.length, currentValue.length); variables[variableName] = currentValue.slice(0, start) + newValue + currentValue.slice(end); if (onChange) onChange({ key: "content", value: JSON.stringify(variables) }); if (stopEditing) stopEditing(); }); textBlock.appendChild(span); }; var getInlineMarkdownFormDisplayText = (runs, variables) => runs.map((run) => substituteVariables(run.text, variables)).join(""); var applyInlineMarkdownStyle = (arg) => { const { element, run, schema, font } = arg; const resolvedFont = resolveFontVariant(run, schema, font); if (resolvedFont.fontName) element.style.fontFamily = `'${resolvedFont.fontName}'`; if (resolvedFont.syntheticBold) { element.style.fontWeight = "800"; element.style.textShadow = SYNTHETIC_BOLD_CSS_TEXT_SHADOW; } if (resolvedFont.syntheticItalic) element.style.fontStyle = "italic"; const textDecorations = []; if (run.href) textDecorations.push("underline"); if (run.strikethrough) textDecorations.push("line-through"); if (textDecorations.length > 0) element.style.textDecoration = textDecorations.join(" "); if (run.code) { element.style.backgroundColor = CODE_BACKGROUND_COLOR; element.style.borderRadius = "2px"; element.style.padding = "0 0.15em"; if (!schema.fontVariants?.code || !font[schema.fontVariants.code]) element.style.fontFamily = resolvedFont.fontName ? `'${resolvedFont.fontName}', monospace` : "monospace"; } }; var createStaticInlineMarkdownElement = (run) => { const href = run.href ? normalizeLinkHref(run.href) : void 0; if (!href) return document.createElement("span"); const anchor = document.createElement("a"); anchor.href = href; if (!getInternalLinkTarget(href)) { anchor.target = "_blank"; anchor.rel = "noopener noreferrer"; } return anchor; }; var appendTextSpan = (arg) => { const { textBlock, text, run, schema, font } = arg; if (!text) return; const span = createStaticInlineMarkdownElement(run); span.textContent = text; applyInlineMarkdownStyle({ element: span, run, schema, font }); textBlock.appendChild(span); }; var appendVariableSpan = (arg) => { const { textBlock, variableName, variables, run, schema, font, theme, onChange, stopEditing } = arg; const span = document.createElement("span"); span.style.outline = `${theme.colorPrimary} dashed 1px`; applyInlineMarkdownStyle({ element: span, run, schema, font }); makeElementPlainTextContentEditable(span); span.textContent = variables[variableName] ?? ""; span.addEventListener("blur", (e) => { const newValue = e.target.textContent || ""; if (newValue !== variables[variableName]) { variables[variableName] = newValue; if (onChange) onChange({ key: "content", value: JSON.stringify(variables) }); if (stopEditing) stopEditing(); } }); textBlock.appendChild(span); }; var renderInlineMarkdownVariableSpans = (arg) => { const { runs, variables, textBlock, schema, font, theme, onChange, stopEditing } = arg; textBlock.innerHTML = ""; runs.forEach((run) => { let lastIndex = 0; visitVariables(run.text, ({ name, startIndex, endIndex }) => { appendTextSpan({ textBlock, text: run.text.slice(lastIndex, startIndex), run, schema, font }); appendVariableSpan({ textBlock, variableName: name, variables, run, schema, font, theme, onChange, stopEditing }); lastIndex = endIndex + 1; }); appendTextSpan({ textBlock, text: run.text.slice(lastIndex), run, schema, font }); }); }; /** * An optimisation to try to minimise jank while typing. * Only check whether variables were modified based on certain key presses. * Regex would otherwise be performed on every key press (which isn't terrible, but this code helps). */ var keyPressShouldBeChecked = (event) => { if (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "ArrowLeft" || event.key === "ArrowRight") return false; const selection = window.getSelection(); const contenteditable = event.target; if (selection?.focusOffset === contenteditable?.textContent?.length) return event.key === "}" || event.key === "Backspace" || event.key === "Delete"; if (selection?.anchorOffset === 0) return event.key === "{" || event.key === "Backspace" || event.key === "Delete"; return true; }; //#endregion //#region src/multiVariableText/index.ts var schema$1 = { pdf: pdfRender$3, ui: uiRender$3, propPanel: propPanel$2, icon: createSvgStr(Type), uninterruptedEditMode: true }; //#endregion //#region src/shapes/rectAndEllipse.ts var shape = { ui: (arg) => { const { schema, rootElement } = arg; const div = document.createElement("div"); div.style.width = "100%"; div.style.height = "100%"; div.style.boxSizing = "border-box"; if (schema.type === "ellipse") div.style.borderRadius = "50%"; else if (schema.radius && schema.radius > 0) div.style.borderRadius = `${schema.radius}mm`; div.style.borderWidth = `${schema.borderWidth ?? 0}mm`; div.style.borderStyle = schema.borderWidth && schema.borderColor ? "solid" : "none"; div.style.borderColor = schema.borderColor ?? "transparent"; div.style.backgroundColor = schema.color ?? "transparent"; rootElement.appendChild(div); }, pdf: (arg) => { const { schema, page, options } = arg; if (!schema.color && !schema.borderColor) return; const { colorType } = options; const cArg = { schema, pageHeight: page.getHeight() }; const { position, width, height, rotate, opacity } = convertForPdfLayoutProps(cArg); const { position: { x: x4Ellipse, y: y4Ellipse } } = convertForPdfLayoutProps({ ...cArg, applyRotateTranslate: false }); const borderWidth = schema.borderWidth ? mm2pt(schema.borderWidth) : 0; const drawOptions = { rotate, borderWidth, borderColor: hex2PrintingColor(schema.borderColor, colorType), color: hex2PrintingColor(schema.color, colorType), opacity, borderOpacity: opacity }; if (schema.type === "ellipse") page.drawEllipse({ x: x4Ellipse + width / 2, y: y4Ellipse + height / 2, xScale: width / 2 - borderWidth / 2, yScale: height / 2 - borderWidth / 2, ...drawOptions }); else if (schema.type === "rectangle") { const radius = schema.radius ?? 0; page.drawRectangle({ x: position.x + borderWidth * ((1 - Math.sin(toRadians(rotate))) / 2) + Math.tan(toRadians(rotate)) * Math.PI ** 2, y: position.y + borderWidth * ((1 + Math.sin(toRadians(rotate))) / 2) + Math.tan(toRadians(rotate)) * Math.PI ** 2, width: width - borderWidth, height: height - borderWidth, ...radius ? { radius: mm2pt(radius) } : {}, ...drawOptions }); } }, propPanel: { schema: ({ i18n }) => ({ borderWidth: { title: i18n("schemas.borderWidth"), type: "number", widget: "inputNumber", props: { min: 0, step: 1 }, span: 12 }, borderColor: { title: i18n("schemas.borderColor"), type: "string", widget: "color", props: { disabledAlpha: true }, rules: [{ pattern: HEX_COLOR_PATTERN, message: i18n("validation.hexColor") }], span: 12 }, color: { title: i18n("schemas.color"), type: "string", widget: "color", props: { disabledAlpha: true }, rules: [{ pattern: HEX_COLOR_PATTERN, message: i18n("validation.hexColor") }] }, radius: { title: i18n("schemas.radius"), type: "number", widget: "inputNumber", props: { min: 0, step: 1 }, span: 12 } }), defaultSchema: { name: "", type: "rectangle", position: { x: 0, y: 0 }, width: 62.5, height: 37.5, rotate: 0, opacity: 1, borderWidth: 1, borderColor: "#000000", color: "", readOnly: true, radius: 0 } } }; var getPropPanelSchema = (type) => ({ ...shape.propPanel, defaultSchema: { ...shape.propPanel.defaultSchema, type } }); var rectangle = { ...shape, propPanel: getPropPanelSchema("rectangle"), icon: createSvgStr(Square) }; var ellipse = { ...shape, propPanel: getPropPanelSchema("ellipse"), icon: createSvgStr(Circle) }; //#endregion //#region src/list/pdfRender.ts var rectanglePdfRender$2 = rectangle.pdf; var pdfRender$2 = async (arg) => { const { schema, value } = arg; const items = normalizeListItems(value); const range = getListItemRange(schema) ?? { start: 0, end: items.length }; const visibleItems = items.slice(range.start, range.end); if (visibleItems.length === 0) return; const layout = await calculateListLayout({ schema, items: visibleItems, markerItems: items, startIndex: range.start, options: arg.options, _cache: arg._cache }); if (schema.backgroundColor) await rectanglePdfRender$2({ ...arg, schema: { ...schema, type: "rectangle", borderWidth: 0, borderColor: "", color: schema.backgroundColor } }); let y = schema.position.y; for (const item of layout.items) { await pdfRender$4({ ...arg, value: item.marker, schema: { ...schema, type: "text", position: { x: schema.position.x + item.markerX, y }, width: layout.markerWidth, height: item.height, backgroundColor: "", alignment: "right", verticalAlignment: "top", dynamicFontSize: void 0 } }); await pdfRender$4({ ...arg, value: item.item, schema: { ...schema, type: "text", position: { x: schema.position.x + item.bodyX, y }, width: item.bodyWidth, height: item.height, backgroundColor: "", verticalAlignment: "top", dynamicFontSize: void 0 } }); y += item.height; } }; //#endregion //#region src/list/propPanel.ts var propPanel$1 = { schema: (propPanelProps) => { if (typeof propPanel$3.schema !== "function") throw new Error("Oops, is text schema no longer a function?"); const parentSchema = propPanel$3.schema(propPanelProps); const i18n = propPanelProps.i18n; const listSchema = { ...parentSchema }; delete listSchema.useDynamicFontSize; delete listSchema.dynamicFontSize; return { ...listSchema, "-------": { type: "void", widget: "Divider" }, listStyle: { title: i18n("schemas.list.listStyle"), type: "string", widget: "select", props: { options: [{ label: i18n("schemas.list.bullet"), value: LIST_STYLE_BULLET }, { label: i18n("schemas.list.ordered"), value: LIST_STYLE_ORDERED }] }, span: 24 }, markerWidth: { title: i18n("schemas.list.markerWidth"), type: "number", widget: "inputNumber", props: { min: 0 }, span: 6 }, markerGap: { title: i18n("schemas.list.markerGap"), type: "number", widget: "inputNumber", props: { min: 0 }, span: 6 }, indentSize: { title: i18n("schemas.list.indentSize"), type: "number", widget: "inputNumber", props: { min: 0 }, span: 6 }, itemSpacing: { title: i18n("schemas.list.itemSpacing"), type: "number", widget: "inputNumber", props: { min: 0 }, span: 6 } }; }, widgets: propPanel$3.widgets, defaultSchema: { ...propPanel$3.defaultSchema, type: "list", content: JSON.stringify(["First item", "Second item"]), width: 80, height: 20, listStyle: DEFAULT_LIST_STYLE, markerWidth: 6, markerGap: 2, indentSize: 6, itemSpacing: 1, dynamicFontSize: void 0, verticalAlignment: "top" } }; //#endregion //#region src/list/uiRender.ts var focusDataKey = "pdfmeListFocusIndex"; var actionDataKey = "pdfmeListAction"; var internalFocusDataKey = "pdfmeListInternalFocus"; var caretMarker = "​"; var pendingFocusIndexes = /* @__PURE__ */ new Map(); var getListFocusKey = (schema) => schema.id || schema.name; var isComposingKeyboardEvent = (event) => event.isComposing || event.keyCode === 229; var getText = (element) => { const rawText = element.innerText; const hasCaretMarker = rawText.includes(caretMarker); let text = rawText.replace(/\u200B/g, ""); if (!hasCaretMarker && text.endsWith("\n")) text = text.slice(0, -1); return text; }; var setStyles = (element, styles) => { Object.assign(element.style, styles); }; var focusBody = (body) => { body.focus(); const selection = window.getSelection(); const range = document.createRange(); if (selection && range) { range.selectNodeContents(body); range.collapse(false); selection.removeAllRanges(); selection.addRange(range); } }; var getCaretRangeFromPoint = (x, y) => { const documentWithCaret = document; if (documentWithCaret.caretRangeFromPoint) return documentWithCaret.caretRangeFromPoint(x, y); const caretPosition = documentWithCaret.caretPositionFromPoint?.(x, y); if (!caretPosition) return null; const range = document.createRange(); range.setStart(caretPosition.offsetNode, caretPosition.offset); range.collapse(true); return range; }; var focusBodyFromMouseEvent = (body, event) => { body.focus(); const range = getCaretRangeFromPoint(event.clientX, event.clientY); if (!range || !body.contains(range.startContainer)) return; const selection = window.getSelection(); if (!selection) return; selection.removeAllRanges(); selection.addRange(range); }; var getBodyEditor = (body) => body.querySelector("[contenteditable], [tabindex]"); var insertLineBreakAtSelection = (element) => { const fallbackText = getText(element); const selection = window.getSelection(); if (!selection?.rangeCount) { element.innerText = `${fallbackText}\n${caretMarker}`; focusBody(element); return true; } const range = selection.getRangeAt(0); if (!element.contains(range.commonAncestorContainer)) { element.innerText = `${fallbackText}\n${caretMarker}`; focusBody(element); return true; } selection.deleteFromDocument(); const fragment = document.createDocumentFragment(); const lineBreak = document.createElement("br"); const marker = document.createTextNode(caretMarker); fragment.append(lineBreak, marker); range.insertNode(fragment); range.setStart(marker, marker.length); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); if (!element.innerText.includes(caretMarker)) { element.innerText = `${fallbackText}\n${caretMarker}`; focusBody(element); } return true; }; var createActionButton = (arg) => { const button = document.createElement("button"); button.type = "button"; button.innerText = arg.label; button.setAttribute("aria-label", arg.ariaLabel); button.disabled = Boolean(arg.disabled); setStyles(button, { width: "18px", height: "18px", display: "inline-flex", alignItems: "center", justifyContent: "center", padding: "0", border: "1px solid #d9d9d9", borderRadius: "3px", background: "#ffffff", color: "#333333", fontSize: "11px", lineHeight: "1", cursor: arg.disabled ? "not-allowed" : "pointer", opacity: arg.disabled ? .45 : 1 }); button.addEventListener("pointerdown", (event) => { arg.onPressStart?.(); event.stopPropagation(); }); button.addEventListener("mousedown", (event) => { arg.onPressStart?.(); event.preventDefault(); event.stopPropagation(); }); button.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); if (!arg.disabled) arg.onClick(); }); return button; }; var uiRender$2 = async (arg) => { const { rootElement, schema, value, mode, onChange, stopEditing, tabIndex, placeholder } = arg; const focusKey = getListFocusKey(schema); const editable = isEditable(mode, schema); const showControls = editable && (mode === "form" || mode === "designer"); const usePlaceholder = editable && !value && Boolean(placeholder); const items = normalizeListItems(usePlaceholder ? placeholder || "" : value); const originalItems = normalizeListItemEntries(value); const range = getListItemRange(schema) ?? { start: 0, end: items.length }; const visibleItems = items.slice(range.start, range.end); const renderItems = visibleItems; rootElement.innerHTML = ""; setStyles(rootElement, { position: "relative", width: "100%", height: "100%", backgroundColor: schema.backgroundColor || "transparent", overflow: "visible" }); const layout = await calculateListLayout({ schema, items: renderItems, markerItems: items, startIndex: range.start, options: arg.options, _cache: arg._cache }); const bodyElements = []; const getEditedItems = () => layout.items.map((item, index) => ({ level: item.level, text: getText(bodyElements[index]) })); const getNextItems = () => { const editedItems = getEditedItems(); if (usePlaceholder) return editedItems; const nextItems = [...originalItems]; nextItems.splice(range.start, visibleItems.length, ...editedItems); return nextItems; }; const commitItems = (nextItems, focusIndex) => { if (!onChange) return; if (focusIndex !== void 0) { rootElement.dataset[focusDataKey] = String(focusIndex); pendingFocusIndexes.set(focusKey, focusIndex); } onChange({ key: "content", value: serializeListItems(nextItems) }); }; const commitHeight = async (focusIndex) => { if (!onChange) return; if (focusIndex !== void 0) { rootElement.dataset[focusDataKey] = String(focusIndex); pendingFocusIndexes.set(focusKey, focusIndex); } const rawItems = normalizeListItems(serializeListItems(getNextItems())); const nextLayout = await calculateListLayout({ schema, items: rawItems.slice(range.start, range.end), markerItems: rawItems, startIndex: range.start, options: arg.options, _cache: arg._cache }); if (schema.height !== nextLayout.totalHeight) onChange({ key: "height", value: nextLayout.totalHeight }); }; const preserveEditingForAction = () => { rootElement.dataset[actionDataKey] = "true"; }; const updateItems = (rowIndex, mutate) => { const nextItems = getNextItems(); if (nextItems.length === 0) nextItems.push({ level: 0, text: "" }); const focusIndex = mutate(nextItems, Math.min(Math.max(range.start + rowIndex, 0), nextItems.length - 1)); preserveEditingForAction(); commitItems(nextItems, focusIndex); }; const preserveEditingForInternalFocus = () => { rootElement.dataset[internalFocusDataKey] = "true"; }; const preserveEditingForKeyboardCommit = () => { preserveEditingForInternalFocus(); setTimeout(() => { if (rootElement.dataset[internalFocusDataKey] === "true") delete rootElement.dataset[internalFocusDataKey]; }); }; const handleInternalFocusPointer = (event) => { preserveEditingForInternalFocus(); event.stopPropagation(); }; const handleBodyMouseDown = (body, event) => { handleInternalFocusPointer(event); focusBodyFromMouseEvent(body, event); }; const appendEmptyListControls = () => { const controls = document.createElement("div"); controls.addEventListener("pointerdown", preserveEditingForAction); controls.addEventListener("mousedown", preserveEditingForAction); setStyles(controls, { position: "absolute", top: "0mm", right: "-20px", display: "flex", gap: "2px" }); controls.appendChild(createActionButton({ label: "+", ariaLabel: arg.i18n("schemas.list.addItem"), onPressStart: preserveEditingForAction, onClick: () => { const nextItems = [...originalItems]; nextItems.splice(range.start, 0, { level: 0, text: "" }); commitItems(nextItems, range.start); } })); rootElement.appendChild(controls); }; let offsetY = 0; for (let index = 0; index < layout.items.length; index += 1) { const item = layout.items[index]; const row = document.createElement("div"); setStyles(row, { position: "absolute", top: `${offsetY}mm`, left: "0mm", width: `${schema.width}mm`, height: `${item.height}mm` }); const marker = document.createElement("div"); setStyles(marker, { position: "absolute", top: "0mm", left: `${item.markerX}mm`, width: `${layout.markerWidth}mm`, height: "100%", backgroundColor: "transparent", cursor: "default" }); const body = document.createElement("div"); setStyles(body, { position: "absolute", top: "0mm", left: `${item.bodyX}mm`, width: `${item.bodyWidth}mm`, height: `${item.height}mm`, backgroundColor: "transparent", cursor: editable ? "text" : "default" }); const schemaForUI = schema; const textSchema = { ...schema, id: `${schemaForUI.id || schema.name}-list-item-${item.itemIndex}`, name: `${schema.name}-list-item-${item.itemIndex}`, type: "text", content: item.item, position: { x: 0, y: 0 }, width: item.bodyWidth, height: item.height, alignment: schema.alignment ?? "left", fontSize: schema.fontSize ?? 13, lineHeight: schema.lineHeight ?? 1, characterSpacing: schema.characterSpacing ?? 0, fontColor: usePlaceholder ? PLACEHOLDER_FONT_COLOR : schema.fontColor || "#000000", backgroundColor: "" }; const markerTextSchema = { ...textSchema, id: `${schemaForUI.id || schema.name}-list-marker-${item.itemIndex}`, name: `${schema.name}-list-marker-${item.itemIndex}`, content: item.marker, width: layout.markerWidth, height: item.height, alignment: "right", fontColor: schema.fontColor || "#000000" }; await uiRender$4({ ...arg, rootElement: marker, schema: markerTextSchema, value: item.marker, mode: "viewer", placeholder: "", onChange: void 0, stopEditing: void 0 }); await uiRender$4({ ...arg, rootElement: body, schema: textSchema, value: item.item, placeholder: "", onChange: void 0, stopEditing: void 0 }); if (editable) { const editor = getBodyEditor(body); if (!editor) throw new Error("Unable to find list item text editor"); editor.tabIndex = tabIndex || 0; body.addEventListener("pointerdown", handleInternalFocusPointer); body.addEventListener("mousedown", (event) => { handleBodyMouseDown(editor, event); }); body.addEventListener("click", (event) => { event.stopPropagation(); focusBodyFromMouseEvent(editor, event); }); editor.addEventListener("focus", () => { if (usePlaceholder) { editor.innerText = ""; editor.style.color = schema.fontColor || "#000000"; } }); body.addEventListener("blur", (event) => { const isListAction = rootElement.dataset[actionDataKey] === "true"; const relatedTarget = event.relatedTarget; const isInternalFocus = rootElement.dataset[internalFocusDataKey] === "true" || relatedTarget instanceof Node && rootElement.contains(relatedTarget); delete rootElement.dataset[internalFocusDataKey]; if (isListAction || isInternalFocus) return; if (!onChange) return; commitItems(getNextItems()); if (stopEditing) stopEditing(); }, true); editor.addEventListener("keydown", (event) => { if (event.key === "Enter") { if (isComposingKeyboardEvent(event)) return; event.preventDefault(); if (insertLineBreakAtSelection(editor)) { preserveEditingForKeyboardCommit(); if (mode === "form") commitHeight(range.start + index); else commitItems(getNextItems(), range.start + index); } } else if (event.key === "Tab") { event.preventDefault(); updateItems(index, (nextItems, itemIndex) => { const itemToUpdate = nextItems[itemIndex]; itemToUpdate.level = event.shiftKey ? Math.max(itemToUpdate.level - 1, 0) : Math.min(itemToUpdate.level + 1, 8); return itemIndex; }); } else if (event.key === "Backspace" && getText(editor) === "") { event.preventDefault(); updateItems(index, (nextItems, itemIndex) => { if (nextItems.length <= 1) { nextItems.splice(0); return; } nextItems.splice(itemIndex, 1); return Math.min(itemIndex, nextItems.length - 1); }); } }); bodyElements.push(editor); } row.appendChild(marker); row.appendChild(body); if (showControls) { const controls = document.createElement("div"); controls.addEventListener("pointerdown", preserveEditingForAction); controls.addEventListener("mousedown", preserveEditingForAction); setStyles(controls, { position: "absolute", top: "0mm", right: "-82px", display: "flex", gap: "2px" }); controls.appendChild(createActionButton({ label: "+", ariaLabel: arg.i18n("schemas.list.addItem"), onPressStart: preserveEditingForAction, onClick: () => { updateItems(index, (nextItems, itemIndex) => { nextItems.splice(itemIndex + 1, 0, { level: nextItems[itemIndex]?.level ?? 0, text: "" }); return itemIndex + 1; }); } })); controls.appendChild(createActionButton({ label: "-", ariaLabel: arg.i18n("schemas.list.removeItem"), onPressStart: preserveEditingForAction, onClick: () => { updateItems(index, (nextItems, itemIndex) => { if (nextItems.length <= 1) { nextItems.splice(0); return; } nextItems.splice(itemIndex, 1); return Math.min(itemIndex, nextItems.length - 1); }); } })); controls.appendChild(createActionButton({ label: "<", ariaLabel: arg.i18n("schemas.list.outdentItem"), disabled: item.level === 0, onPressStart: preserveEditingForAction, onClick: () => { updateItems(index, (nextItems, itemIndex) => { nextItems[itemIndex].level = Math.max(nextItems[itemIndex].level - 1, 0); return itemIndex; }); } })); controls.appendChild(createActionButton({ label: ">", ariaLabel: arg.i18n("schemas.list.indentItem"), disabled: item.level >= 8, onPressStart: preserveEditingForAction, onClick: () => { updateItems(index, (nextItems, itemIndex) => { nextItems[itemIndex].level = Math.min(nextItems[itemIndex].level + 1, 8); return itemIndex; }); } })); row.appendChild(controls); } rootElement.appendChild(row); offsetY += item.height; } if (showControls && visibleItems.length === 0) appendEmptyListControls(); const pendingFocusIndex = pendingFocusIndexes.get(focusKey); if (pendingFocusIndex !== void 0) pendingFocusIndexes.delete(focusKey); const requestedFocusIndex = Number(rootElement.dataset[focusDataKey] ?? pendingFocusIndex); delete rootElement.dataset[focusDataKey]; delete rootElement.dataset[actionDataKey]; delete rootElement.dataset[internalFocusDataKey]; const relativeFocusIndex = requestedFocusIndex - range.start; if (editable && Number.isFinite(requestedFocusIndex) && bodyElements[relativeFocusIndex]) setTimeout(() => focusBody(bodyElements[relativeFocusIndex])); else if (editable && mode === "designer" && bodyElements[0]) setTimeout(() => { if (!rootElement.contains(document.activeElement)) focusBody(bodyElements[0]); }); if (schema.height !== layout.totalHeight && onChange) onChange({ key: "height", value: layout.totalHeight }); }; //#endregion //#region src/list/index.ts var listSchema = { pdf: pdfRender$2, ui: uiRender$2, propPanel: propPanel$1, icon: createSvgStr(List) }; //#endregion //#region src/graphics/imagehelper.ts var decoder = new TextDecoder(); var toUTF8String = (input, start = 0, end = input.length) => decoder.decode(input.slice(start, end)); var toHexString = (input, start = 0, end = input.length) => input.slice(start, end).reduce((memo, i) => memo + ("0" + i.toString(16)).slice(-2), ""); var readUInt16BE = (input, offset = 0) => input[offset] * 2 ** 8 + input[offset + 1]; var readUInt32BE = (input, offset = 0) => input[offset] * 2 ** 24 + input[offset + 1] * 2 ** 16 + input[offset + 2] * 2 ** 8 + input[offset + 3]; var extractSize = (input, index) => { return { height: readUInt16BE(input, index), width: readUInt16BE(input, index + 2) }; }; var validateInput = (input, index) => { if (index > input.length) throw new TypeError("Corrupt JPG, exceeded buffer limits"); if (input[index] !== 255) throw new TypeError("Invalid JPG, marker table corrupted"); }; var JPG = { validate: (input) => toHexString(input, 0, 2) === "ffd8", calculate(input) { input = input.slice(4); let next; while (input.length) { const i = readUInt16BE(input, 0); validateInput(input, i); next = input[i + 1]; if (next === 192 || next === 193 || next === 194) return extractSize(input, i + 5); input = input.slice(i + 2); } throw new TypeError("Invalid JPG, no size found"); } }; var pngSignature = "PNG\r\n\n"; var pngImageHeaderChunkName = "IHDR"; var pngFriedChunkName = "CgBI"; var typeHandlers = { jpg: JPG, png: { validate(input) { if (pngSignature === toUTF8String(input, 1, 8)) { let chunkName = toUTF8String(input, 12, 16); if (chunkName === pngFriedChunkName) chunkName = toUTF8String(input, 28, 32); if (chunkName !== pngImageHeaderChunkName) throw new TypeError("Invalid PNG"); return true; } return false; }, calculate(input) { if (toUTF8String(input, 12, 16) === pngFriedChunkName) return { height: readUInt32BE(input, 36), width: readUInt32BE(input, 32) }; return { height: readUInt32BE(input, 20), width: readUInt32BE(input, 16) }; } } }; function detector(input) { const firstBytes = { 137: "png", 255: "jpg" }; const byte = input[0]; if (byte in firstBytes) { const type = firstBytes[byte]; if (type && typeHandlers[type].validate(input)) return type; } return Object.keys(typeHandlers).find((key) => typeHandlers[key].validate(input)); } var getImageDimension = (value) => { const idx = value.indexOf(";base64,"); const imgBase64 = value.substring(idx + 8, value.length); return imageSize(Buffer$1.from(imgBase64, "base64")); }; var imageSize = (imgBuffer) => { const type = detector(imgBuffer); if (typeof type !== "undefined" && type in typeHandlers) { const size = typeHandlers[type].calculate(imgBuffer); if (size !== void 0) return size; } throw new TypeError("[@pdfme/schemas/images] Unsupported file type: " + (type === void 0 ? "undefined" : type)); }; //#endregion //#region src/graphics/image.ts /** * Build a short fingerprint for a potentially-large base64 image string. * Previously `${schema.type}${input}` was used, pinning multi-MB base64 * strings in the cache Map forever — every unique image input created a * permanent Map key whose byte length matched the image itself. * * The fingerprint is an FNV-1a 32-bit hash over the full input, combined * with the schema type and input byte length. An earlier revision sampled * three 16-char regions (first + middle + last) instead of hashing, but * the first-16 slice is a constant data-URI prefix for any image of the * same MIME type (`data:image/png;b…` / `data:image/jpeg…`), contributing * no entropy. Hashing every byte removes that weakness at the same O(n) * cost, without