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,323 lines (1,322 loc) 42.2 kB
import { $ as VERTICAL_ALIGN_MIDDLE, A as ALIGN_RIGHT, B as DYNAMIC_FIT_VERTICAL, C as getBoxInsets, D as ALIGN_CENTER, E as hasBoxDimension, F as DEFAULT_FONT_COLOR, G as PLACEHOLDER_FONT_COLOR, H as FONT_VARIANT_FALLBACK_ERROR, I as DEFAULT_FONT_VARIANT_FALLBACK, J as TEXT_FORMAT_INLINE_MARKDOWN, K as SYNTHETIC_BOLD_CSS_TEXT_SHADOW, L as DEFAULT_TEXT_FORMAT, M as CODE_HORIZONTAL_PADDING, N as DEFAULT_ALIGNMENT, O as ALIGN_JUSTIFY, P as DEFAULT_DYNAMIC_FIT, Q as VERTICAL_ALIGN_BOTTOM, R as DEFAULT_TEXT_OVERFLOW, S as getBoxDimensionPropPanelSchema, U as FONT_VARIANT_FALLBACK_PLAIN, W as FONT_VARIANT_FALLBACK_SYNTHETIC, X as TEXT_OVERFLOW_EXPAND, Y as TEXT_FORMAT_PLAIN, Z as TEXT_OVERFLOW_VISIBLE, _ as isFirefox, b as createBoxDimension, d as calculateDynamicFontSize, f as fetchRemoteFontData, g as heightOfFontAtSize, h as getFontKitFont, j as CODE_BACKGROUND_COLOR, m as getFontDescentInPt, p as getBrowserVerticalFontAdjustments, q as SYNTHETIC_BOLD_OFFSET_RATIO, u as getTextLineRange, v as splitTextToSize, x as getBoxContentArea, y as widthOfTextAtSize, z as DYNAMIC_FIT_HORIZONTAL } from "./splitRange-DmVDtmzO.js"; import { _ as stripInlineMarkdown, d as isInlineMarkdownTextSchema, f as layoutRichTextLines, g as parseInlineMarkdown, l as calculateDynamicRichTextFontSize, m as resolveRichTextRuns, s as plainTextLinesToValue, t as applyTextLineRange, u as countRichTextLineGraphemes } from "./measure-L5diay3k.js"; import { c as HEX_COLOR_PATTERN } from "./dynamicTemplate-B4GCNLF9.js"; import { c as isEditable, i as createSvgStr, n as convertForPdfLayoutProps, o as hex2PrintingColor, u as rotatePoint } from "./utils-zDZkqBnX.js"; import { DEFAULT_FONT_NAME, getDefaultFont, getFallbackFontName, getInternalLinkTarget, isBlankPdf, mm2pt, normalizeLinkHref, normalizeSafeLinkUri, registerInternalLinkAnnotation } from "@pdfme/common"; import { PDFName, PDFString } from "@pdfme/pdf-lib"; import { AlignCenter, AlignJustify, AlignLeft, AlignRight, ArrowDownToLine, ArrowUpToLine, Strikethrough, TextCursorInput, Underline } from "lucide"; //#region src/text/linkAnnotation.ts var addUriLinkAnnotation = (arg) => { const { pdfDoc, page, uri, rect, borderWidth = 0 } = arg; const safeUri = normalizeSafeLinkUri(uri); if (!safeUri || rect.width <= 0 || rect.height <= 0) return; const annotationRef = pdfDoc.context.register(pdfDoc.context.obj({ Type: PDFName.of("Annot"), Subtype: PDFName.of("Link"), Rect: [ rect.x, rect.y, rect.x + rect.width, rect.y + rect.height ], Border: [ 0, 0, borderWidth ], A: { Type: PDFName.of("Action"), S: PDFName.of("URI"), URI: PDFString.of(safeUri) } })); page.node.addAnnot(annotationRef); }; //#endregion //#region src/text/richTextPdfRender.ts var getSyntheticBoldWidth = (run, fontSize) => run.syntheticBold ? fontSize * SYNTHETIC_BOLD_OFFSET_RATIO * 2 : 0; var getSyntheticItalicWidth = (run, fontSize) => run.syntheticItalic ? heightOfFontAtSize(run.fontKitFont, fontSize) * Math.tan(12 * Math.PI / 180) : 0; var getRunWidth = (run, fontSize, characterSpacing) => widthOfTextAtSize(run.text, run.fontKitFont, fontSize, characterSpacing) + getSyntheticBoldWidth(run, fontSize) + getSyntheticItalicWidth(run, fontSize) + (run.code ? CODE_HORIZONTAL_PADDING * 2 : 0); var getPdfFontFromObj = (run, pdfFontObj) => { const pdfFont = pdfFontObj[run.fontName]; if (!pdfFont) throw new Error(`[@pdfme/schemas] Missing embedded font "${run.fontName}".`); return pdfFont; }; var embedFontsForRuns = async (runs, embedPdfFont) => { const fontNames = Array.from(new Set(runs.map((run) => run.fontName))); const pdfFonts = await Promise.all(fontNames.map(async (fontName) => [fontName, await embedPdfFont(fontName)])); return Object.fromEntries(pdfFonts); }; var drawDecorationLine = (arg) => { const { page, x, y, width, rotate, pivotPoint, fontSize, color, opacity } = arg; if (width <= 0) return; page.drawLine({ start: rotatePoint({ x, y }, pivotPoint, rotate.angle), end: rotatePoint({ x: x + width, y }, pivotPoint, rotate.angle), thickness: 1 / 12 * fontSize, color, opacity }); }; var getAxisAlignedRect = (arg) => { const { x, y, width, height, rotate, pivotPoint } = arg; if (rotate.angle === 0) return { x, y, width, height }; const points = [ { x, y }, { x: x + width, y }, { x: x + width, y: y + height }, { x, y: y + height } ].map((point) => rotatePoint(point, pivotPoint, rotate.angle)); const xs = points.map((point) => point.x); const ys = points.map((point) => point.y); const minX = Math.min(...xs); const minY = Math.min(...ys); return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY }; }; var getLinkAnnotationRect = (arg) => { const { run, x, y, width, rotate, pivotPoint, fontSize } = arg; const textHeight = heightOfFontAtSize(run.fontKitFont, fontSize); const descent = getFontDescentInPt(run.fontKitFont, fontSize); return getAxisAlignedRect({ x, y: y + descent, width, height: textHeight - descent, rotate, pivotPoint }); }; var drawRun = (arg) => { const { page, pdfLib, run, pdfFont, x, y, rotate, pivotPoint, fontSize, lineHeight, color, opacity, colorType, characterSpacing, strikethrough, underline } = arg; const runWidth = getRunWidth(run, fontSize, characterSpacing); const codePadding = run.code ? CODE_HORIZONTAL_PADDING : 0; const textX = x + codePadding; const textWidth = runWidth - codePadding * 2; const textHeight = heightOfFontAtSize(run.fontKitFont, fontSize); if (run.code) { const bgX = x; const bgY = y - textHeight * .2; const bgPoint = rotate.angle === 0 ? { x: bgX, y: bgY } : rotatePoint({ x: bgX, y: bgY }, pivotPoint, rotate.angle); page.drawRectangle({ x: bgPoint.x, y: bgPoint.y, width: runWidth, height: textHeight * 1.2, rotate, color: hex2PrintingColor(CODE_BACKGROUND_COLOR, colorType), opacity }); } if (strikethrough && runWidth > 0) drawDecorationLine({ page, x: textX, y: y + textHeight / 3, width: textWidth, rotate, pivotPoint, fontSize, color, opacity }); if (underline && runWidth > 0) drawDecorationLine({ page, x: textX, y: y - textHeight / 12, width: textWidth, rotate, pivotPoint, fontSize, color, opacity }); const drawAt = (drawX) => { const point = rotate.angle === 0 ? { x: drawX, y } : rotatePoint({ x: drawX, y }, pivotPoint, rotate.angle); page.drawText(run.text, { x: point.x, y: point.y, rotate, size: fontSize, color, lineHeight: lineHeight * fontSize, font: pdfFont, opacity, ...run.syntheticItalic ? { ySkew: pdfLib.degrees(12) } : {} }); }; drawAt(textX); if (run.syntheticBold) { const offset = fontSize * SYNTHETIC_BOLD_OFFSET_RATIO; for (let i = 1; i <= 2; i++) drawAt(textX + offset * i); } }; var renderInlineMarkdownText = async (arg) => { const { value, schema, font, embedPdfFont, fontKitFont, pdfDoc, page, pdfLib, _cache, colorType, fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing, x, y, width, height, pivotPoint, rotate, opacity } = arg; const allLines = layoutRichTextLines({ runs: await resolveRichTextRuns({ runs: parseInlineMarkdown(value), schema, font, _cache }), fontSize, characterSpacing, boxWidthInPt: width }); const lineRange = getTextLineRange(schema); const lines = applyTextLineRange(allLines, lineRange); const lineRangeStart = lineRange?.start ?? 0; const pdfFontObj = await embedFontsForRuns(lines.flatMap((line) => line.runs), embedPdfFont); const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize); const descent = getFontDescentInPt(fontKitFont, fontSize); const halfLineHeightAdjustment = lineHeight === 0 ? 0 : (lineHeight - 1) * fontSize / 2; let yOffset = 0; if (verticalAlignment === "top") yOffset = firstLineTextHeight + halfLineHeightAdjustment; else { const otherLinesHeight = lineHeight * fontSize * (lines.length - 1); if (verticalAlignment === "bottom") yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment; else if (verticalAlignment === "middle") yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight; } lines.forEach((line, rowIndex) => { if (line.runs.length === 0) return; let textWidth = line.width; let spacing = characterSpacing; if (alignment === "justify" && !line.hardBreak && lineRangeStart + rowIndex < allLines.length - 1) { const graphemeCount = countRichTextLineGraphemes(line); if (graphemeCount > 0) { spacing += (width - textWidth) / graphemeCount; textWidth = width; } } let xLine = x; if (alignment === "center") xLine += (width - textWidth) / 2; else if (alignment === "right") xLine += width - textWidth; const yLine = y + height - yOffset - lineHeight * fontSize * rowIndex; page.pushOperators(pdfLib.setCharacterSpacing(spacing)); if (schema.strikethrough || schema.underline) { const textHeight = Math.max(...line.runs.map((run) => heightOfFontAtSize(run.fontKitFont, fontSize))); if (schema.strikethrough) drawDecorationLine({ page, x: xLine, y: yLine + textHeight / 3, width: textWidth, rotate, pivotPoint, fontSize, color, opacity }); if (schema.underline) drawDecorationLine({ page, x: xLine, y: yLine - textHeight / 12, width: textWidth, rotate, pivotPoint, fontSize, color, opacity }); } line.runs.reduce((currentX, run, runIndex) => { const runWidth = getRunWidth(run, fontSize, spacing); drawRun({ page, pdfLib, run, pdfFont: getPdfFontFromObj(run, pdfFontObj), x: currentX, y: yLine, rotate, pivotPoint, fontSize, lineHeight, color, opacity, colorType, characterSpacing: spacing, strikethrough: Boolean(run.strikethrough), underline: Boolean(run.href) && !schema.underline }); if (run.href) { const rect = getLinkAnnotationRect({ run, x: currentX, y: yLine, width: runWidth, rotate, pivotPoint, fontSize }); const targetName = getInternalLinkTarget(run.href); if (targetName) registerInternalLinkAnnotation({ _cache, page, targetName, rect }); else addUriLinkAnnotation({ pdfDoc, page, uri: run.href, rect }); } return currentX + runWidth + (runIndex === line.runs.length - 1 ? 0 : spacing); }, xLine); }); }; //#endregion //#region src/text/overflow.ts var TEXT_OVERFLOW_EXPAND_SCHEMA_TYPES = new Set(["text", "multiVariableText"]); var isTextOverflowExpandSchema = (schema) => schema.type === void 0 || TEXT_OVERFLOW_EXPAND_SCHEMA_TYPES.has(schema.type); var canUseTextOverflowExpand = (schema, basePdf) => !isTextOverflowExpandSchema(schema) || basePdf === void 0 || isBlankPdf(basePdf); var isTextOverflowExpand = (schema, basePdf) => canUseTextOverflowExpand(schema, basePdf) && schema.overflow === "expand"; var shouldUseDynamicFontSize = (schema, basePdf) => Boolean(schema.dynamicFontSize) && !isTextOverflowExpand(schema, basePdf); //#endregion //#region src/text/pdfRender.ts var PDF_FONT_CACHE_KEY = "text-pdf-font-cache"; var getPdfFontCache = (_cache) => { let pdfFontCache = _cache.get(PDF_FONT_CACHE_KEY); if (!pdfFontCache) { pdfFontCache = {}; _cache.set(PDF_FONT_CACHE_KEY, pdfFontCache); } return pdfFontCache; }; var embedAndGetFont = (arg) => { const { pdfDoc, font, fontName, _cache } = arg; const pdfFontCache = getPdfFontCache(_cache); const cachedFont = pdfFontCache[fontName]; if (cachedFont) return cachedFont; const fontValue = font[fontName]; if (!fontValue) return Promise.reject(/* @__PURE__ */ new Error(`[@pdfme/schemas] Font "${fontName}" is not found.`)); const pdfFontPromise = (async () => { let fontData = fontValue.data; if (typeof fontData === "string" && fontData.startsWith("http")) fontData = await fetchRemoteFontData(fontData); return pdfDoc.embedFont(fontData, { subset: typeof fontValue.subset === "undefined" ? true : fontValue.subset }); })(); pdfFontCache[fontName] = pdfFontPromise; return pdfFontPromise; }; var getFontProp = ({ value, fontKitFont, schema, basePdf, colorType, fontSize: resolvedFontSize }) => { const fontSize = resolvedFontSize ?? (shouldUseDynamicFontSize(schema, basePdf) ? calculateDynamicFontSize({ textSchema: schema, fontKitFont, value }) : schema.fontSize ?? 13); const color = hex2PrintingColor(schema.fontColor || "#000000", colorType); return { alignment: schema.alignment ?? "left", verticalAlignment: schema.verticalAlignment ?? "top", lineHeight: schema.lineHeight ?? 1, characterSpacing: schema.characterSpacing ?? 0, fontSize, color }; }; var graphemeSegmenter; var getGraphemeSegmenter = () => { graphemeSegmenter ?? (graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" })); return graphemeSegmenter; }; var pdfRender = async (arg) => { const { value, pdfDoc, pdfLib, page, options, schema, basePdf, _cache } = arg; const { font = getDefaultFont(), colorType } = options; const pageHeight = page.getHeight(); const { width, height, rotate, position: { x, y }, opacity } = convertForPdfLayoutProps({ schema, pageHeight, applyRotateTranslate: false }); const pivotPoint = { x: x + width / 2, y: pageHeight - mm2pt(schema.position.y) - height / 2 }; drawTextBoxDecoration({ page, schema, colorType, x, y, width, height, rotate, pivotPoint }); if (!value) return; const fontName = schema.fontName ? schema.fontName : getFallbackFontName(font); const enableInlineMarkdown = isInlineMarkdownTextSchema(schema); const contentArea = getBoxContentArea(schema); const contentX = x + mm2pt(contentArea.leftInset); const contentY = y + mm2pt(contentArea.bottomInset); const contentWidth = mm2pt(contentArea.width); const contentHeight = mm2pt(contentArea.height); const pdfFontValuePromise = enableInlineMarkdown ? void 0 : embedAndGetFont({ pdfDoc, font, fontName, _cache }); const fontKitFont = await getFontKitFont(schema.fontName, font, _cache); const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } = getFontProp({ value: enableInlineMarkdown ? stripInlineMarkdown(value) : value, fontKitFont, schema, basePdf, colorType, fontSize: enableInlineMarkdown && shouldUseDynamicFontSize(schema, basePdf) ? await calculateDynamicRichTextFontSize({ value, schema, font, _cache }) : void 0 }); if (enableInlineMarkdown) { await renderInlineMarkdownText({ value, schema, font, embedPdfFont: (fontName) => embedAndGetFont({ pdfDoc, font, fontName, _cache }), fontKitFont, pdfDoc, page, pdfLib, _cache, colorType, fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing, x: contentX, y: contentY, width: contentWidth, height: contentHeight, pivotPoint, rotate, opacity }); return; } if (!pdfFontValuePromise) throw new Error("[@pdfme/schemas] Failed to prepare PDF font for text rendering."); const pdfFontValue = await pdfFontValuePromise; const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize); const descent = getFontDescentInPt(fontKitFont, fontSize); const halfLineHeightAdjustment = lineHeight === 0 ? 0 : (lineHeight - 1) * fontSize / 2; const lines = applyTextLineRange(splitTextToSize({ value, characterSpacing, fontSize, fontKitFont, boxWidthInPt: contentWidth }), getTextLineRange(schema)); const needsTextWidth = alignment !== "left" || Boolean(schema.strikethrough || schema.underline); const needsTextHeight = Boolean(schema.strikethrough || schema.underline); let yOffset = 0; if (verticalAlignment === "top") yOffset = firstLineTextHeight + halfLineHeightAdjustment; else { const otherLinesHeight = lineHeight * fontSize * (lines.length - 1); if (verticalAlignment === "bottom") yOffset = contentHeight - otherLinesHeight + descent - halfLineHeightAdjustment; else if (verticalAlignment === "middle") yOffset = (contentHeight - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight; } lines.forEach((line, rowIndex) => { const trimmed = line.replace("\n", ""); const textWidth = needsTextWidth ? widthOfTextAtSize(trimmed, fontKitFont, fontSize, characterSpacing) : 0; const textHeight = needsTextHeight ? heightOfFontAtSize(fontKitFont, fontSize) : 0; const rowYOffset = lineHeight * fontSize * rowIndex; if (line === "") line = "\r\n"; let xLine = contentX; if (alignment === "center") xLine += (contentWidth - textWidth) / 2; else if (alignment === "right") xLine += contentWidth - textWidth; let yLine = contentY + contentHeight - yOffset - rowYOffset; if (schema.strikethrough && textWidth > 0) { const _x = xLine + textWidth + 1; const _y = yLine + textHeight / 3; page.drawLine({ start: rotatePoint({ x: xLine, y: _y }, pivotPoint, rotate.angle), end: rotatePoint({ x: _x, y: _y }, pivotPoint, rotate.angle), thickness: 1 / 12 * fontSize, color, opacity }); } if (schema.underline && textWidth > 0) { const _x = xLine + textWidth + 1; const _y = yLine - textHeight / 12; page.drawLine({ start: rotatePoint({ x: xLine, y: _y }, pivotPoint, rotate.angle), end: rotatePoint({ x: _x, y: _y }, pivotPoint, rotate.angle), thickness: 1 / 12 * fontSize, color, opacity }); } if (rotate.angle !== 0) { const rotatedPoint = rotatePoint({ x: xLine, y: yLine }, pivotPoint, rotate.angle); xLine = rotatedPoint.x; yLine = rotatedPoint.y; } let spacing = characterSpacing; if (alignment === "justify" && line.slice(-1) !== "\n") { const iterator = getGraphemeSegmenter().segment(trimmed)[Symbol.iterator](); const len = Array.from(iterator).length; spacing += (contentWidth - textWidth) / len; } page.pushOperators(pdfLib.setCharacterSpacing(spacing)); page.drawText(trimmed, { x: xLine, y: yLine, rotate, size: fontSize, color, lineHeight: lineHeight * fontSize, font: pdfFontValue, opacity }); }); }; var drawTextBoxDecoration = (arg) => { const { page, schema, colorType, x, y, width, height, rotate, pivotPoint } = arg; const { borderWidth } = getBoxInsets(schema); const opacity = schema.opacity ?? 1; const drawRectangle = (rect) => { if (rect.width <= 0 || rect.height <= 0) return; const point = rotate.angle === 0 ? { x: rect.x, y: rect.y } : rotatePoint({ x: rect.x, y: rect.y }, pivotPoint, rotate.angle); page.drawRectangle({ x: point.x, y: point.y, width: rect.width, height: rect.height, rotate, color: rect.color, opacity }); }; if (schema.backgroundColor) { const color = hex2PrintingColor(schema.backgroundColor, colorType); if (color) drawRectangle({ x, y, width, height, color }); } if (!schema.borderColor || !hasBoxDimension(schema.borderWidth)) return; const color = hex2PrintingColor(schema.borderColor, colorType); if (!color) return; const top = mm2pt(borderWidth.top); const right = mm2pt(borderWidth.right); const bottom = mm2pt(borderWidth.bottom); const left = mm2pt(borderWidth.left); drawRectangle({ x, y: y + height - top, width, height: top, color }); drawRectangle({ x: x + width - right, y, width: right, height, color }); drawRectangle({ x, y, width, height: bottom, color }); drawRectangle({ x, y, width: left, height, color }); }; //#endregion //#region src/text/icons/index.ts var TextStrikethroughIcon = createSvgStr(Strikethrough); var TextUnderlineIcon = createSvgStr(Underline); var TextAlignLeftIcon = createSvgStr(AlignLeft); var TextAlignCenterIcon = createSvgStr(AlignCenter); var TextAlignRightIcon = createSvgStr(AlignRight); var TextAlignJustifyIcon = createSvgStr(AlignJustify); var TextVerticalAlignTopIcon = createSvgStr(ArrowUpToLine); var TextVerticalAlignMiddleIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M8 19h3v4h2v-4h3l-4-4l-4 4zm8-14h-3V1h-2v4H8l4 4l4-4zM4 11v2h16v-2H4z" fill="currentColor"></path></svg>`; var TextVerticalAlignBottomIcon = createSvgStr(ArrowDownToLine); //#endregion //#region src/text/extraFormatter.ts var Formatter = /* @__PURE__ */ function(Formatter) { Formatter["STRIKETHROUGH"] = "strikethrough"; Formatter["UNDERLINE"] = "underline"; Formatter["ALIGNMENT"] = "alignment"; Formatter["VERTICAL_ALIGNMENT"] = "verticalAlignment"; return Formatter; }({}); function getExtraFormatterSchema(i18n) { const buttons = [ { key: Formatter.STRIKETHROUGH, icon: TextStrikethroughIcon, type: "boolean" }, { key: Formatter.UNDERLINE, icon: TextUnderlineIcon, type: "boolean" }, { key: Formatter.ALIGNMENT, icon: TextAlignLeftIcon, type: "select", value: DEFAULT_ALIGNMENT }, { key: Formatter.ALIGNMENT, icon: TextAlignCenterIcon, type: "select", value: ALIGN_CENTER }, { key: Formatter.ALIGNMENT, icon: TextAlignRightIcon, type: "select", value: ALIGN_RIGHT }, { key: Formatter.ALIGNMENT, icon: TextAlignJustifyIcon, type: "select", value: ALIGN_JUSTIFY }, { key: Formatter.VERTICAL_ALIGNMENT, icon: TextVerticalAlignTopIcon, type: "select", value: "top" }, { key: Formatter.VERTICAL_ALIGNMENT, icon: TextVerticalAlignMiddleIcon, type: "select", value: VERTICAL_ALIGN_MIDDLE }, { key: Formatter.VERTICAL_ALIGNMENT, icon: TextVerticalAlignBottomIcon, type: "select", value: VERTICAL_ALIGN_BOTTOM } ]; return { title: i18n("schemas.text.format"), widget: "ButtonGroup", buttons, span: 24 }; } //#endregion //#region src/text/propPanel.ts var UseDynamicFontSize = (props) => { const { rootElement, changeSchemas, activeSchema, i18n, basePdf } = props; const isExpand = isTextOverflowExpand(activeSchema, basePdf); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = !isExpand && Boolean(activeSchema?.dynamicFontSize); checkbox.disabled = isExpand; checkbox.onchange = (e) => { changeSchemas([{ key: "dynamicFontSize", value: e.target.checked ? { min: 4, max: 72, fit: DEFAULT_DYNAMIC_FIT } : void 0, schemaId: activeSchema.id }]); }; const label = document.createElement("label"); const span = document.createElement("span"); span.innerText = i18n("schemas.text.dynamicFontSize") || ""; span.style.cssText = "margin-left: 0.5rem"; label.style.cssText = "display: flex; width: 100%;"; label.style.opacity = isExpand ? "0.5" : "1"; label.appendChild(checkbox); label.appendChild(span); rootElement.appendChild(label); }; var UseInlineMarkdown = (props) => { const { rootElement, changeSchemas, activeSchema, i18n } = props; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = activeSchema?.textFormat === TEXT_FORMAT_INLINE_MARKDOWN; checkbox.onchange = (e) => { changeSchemas([{ key: "textFormat", value: e.target.checked ? TEXT_FORMAT_INLINE_MARKDOWN : TEXT_FORMAT_PLAIN, schemaId: activeSchema.id }]); }; const label = document.createElement("label"); const span = document.createElement("span"); span.innerText = i18n("schemas.text.inlineMarkdown") || ""; span.style.cssText = "margin-left: 0.5rem"; label.style.cssText = "display: flex; width: 100%;"; label.appendChild(checkbox); label.appendChild(span); rootElement.appendChild(label); }; var propPanel = { schema: ({ options, activeSchema, i18n, basePdf }) => { const font = options.font || { [DEFAULT_FONT_NAME]: { data: "", fallback: true } }; const fontNames = Object.keys(font); const fallbackFontName = getFallbackFontName(font); const activeTextSchema = activeSchema; const canExpandOverflow = canUseTextOverflowExpand(activeTextSchema, basePdf); const enableDynamicFont = !isTextOverflowExpand(activeTextSchema, basePdf) && Boolean(activeSchema?.dynamicFontSize); const hideTextFormat = activeTextSchema.type === "text" && activeTextSchema.readOnly !== true; const enableInlineMarkdown = activeTextSchema.textFormat === "inline-markdown" && !hideTextFormat; const baseFontName = activeTextSchema.fontName && font[activeTextSchema.fontName] ? activeTextSchema.fontName : fallbackFontName; const optionalFontNames = [{ label: baseFontName, value: "" }, ...fontNames.filter((name) => name !== baseFontName).map((name) => ({ label: name, value: name }))]; const overflowOptions = [{ label: i18n("schemas.text.overflowVisible"), value: TEXT_OVERFLOW_VISIBLE }, ...canExpandOverflow ? [{ label: i18n("schemas.text.overflowExpand"), value: TEXT_OVERFLOW_EXPAND }] : []]; return { fontName: { title: i18n("schemas.text.fontName"), type: "string", widget: "select", default: fallbackFontName, placeholder: fallbackFontName, props: { options: fontNames.map((name) => ({ label: name, value: name })) }, span: 12 }, fontSize: { title: i18n("schemas.text.size"), type: "number", widget: "inputNumber", span: 6, disabled: enableDynamicFont, props: { min: 0 } }, characterSpacing: { title: i18n("schemas.text.spacing"), type: "number", widget: "inputNumber", span: 6, props: { min: 0 } }, formatter: getExtraFormatterSchema(i18n), lineHeight: { title: i18n("schemas.text.lineHeight"), type: "number", widget: "inputNumber", props: { step: .1, min: 0 }, span: 8 }, overflow: { title: i18n("schemas.text.overflow"), type: "string", widget: "select", default: DEFAULT_TEXT_OVERFLOW, props: { options: overflowOptions }, span: 8 }, useDynamicFontSize: { type: "boolean", widget: "UseDynamicFontSize", bind: false, span: 16 }, dynamicFontSize: { type: "object", widget: "card", column: 3, properties: { min: { title: i18n("schemas.text.min"), type: "number", widget: "inputNumber", hidden: !enableDynamicFont, props: { min: 0 } }, max: { title: i18n("schemas.text.max"), type: "number", widget: "inputNumber", hidden: !enableDynamicFont, props: { min: 0 } }, fit: { title: i18n("schemas.text.fit"), type: "string", widget: "select", hidden: !enableDynamicFont, props: { options: [{ label: i18n("schemas.horizontal"), value: DYNAMIC_FIT_HORIZONTAL }, { label: i18n("schemas.vertical"), value: DYNAMIC_FIT_VERTICAL }] } } } }, fontColor: { title: i18n("schemas.textColor"), 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") }] }, borderColor: { title: i18n("schemas.borderColor"), type: "string", widget: "color", props: { disabledAlpha: true }, rules: [{ pattern: HEX_COLOR_PATTERN, message: i18n("validation.hexColor") }] }, borderWidth: { title: i18n("schemas.borderWidth"), type: "object", widget: "lineTitle", span: 24, properties: getBoxDimensionPropPanelSchema(.1) }, padding: { title: i18n("schemas.padding"), type: "object", widget: "lineTitle", span: 24, properties: getBoxDimensionPropPanelSchema() }, useInlineMarkdown: { type: "boolean", widget: "UseInlineMarkdown", bind: false, hidden: hideTextFormat, span: enableInlineMarkdown ? 12 : 24 }, fontVariantFallback: { title: i18n("schemas.text.variantFallback"), type: "string", widget: "select", default: DEFAULT_FONT_VARIANT_FALLBACK, hidden: !enableInlineMarkdown, props: { options: [ { label: i18n("schemas.text.synthetic"), value: FONT_VARIANT_FALLBACK_SYNTHETIC }, { label: i18n("schemas.text.plain"), value: FONT_VARIANT_FALLBACK_PLAIN }, { label: i18n("schemas.text.error"), value: FONT_VARIANT_FALLBACK_ERROR } ] }, span: 12 }, fontVariants: { title: i18n("schemas.text.markdownFonts"), type: "object", widget: "card", column: 2, hidden: !enableInlineMarkdown, properties: { bold: { title: i18n("schemas.text.boldFont"), type: "string", widget: "select", props: { options: optionalFontNames } }, italic: { title: i18n("schemas.text.italicFont"), type: "string", widget: "select", props: { options: optionalFontNames } }, boldItalic: { title: i18n("schemas.text.boldItalicFont"), type: "string", widget: "select", props: { options: optionalFontNames } }, code: { title: i18n("schemas.text.codeFont"), type: "string", widget: "select", props: { options: optionalFontNames } } } } }; }, widgets: { UseDynamicFontSize, UseInlineMarkdown }, defaultSchema: { name: "", type: "text", content: "Type Something...", position: { x: 0, y: 0 }, width: 45, height: 10, rotate: 0, alignment: DEFAULT_ALIGNMENT, verticalAlignment: "top", fontSize: 13, textFormat: DEFAULT_TEXT_FORMAT, overflow: DEFAULT_TEXT_OVERFLOW, fontVariantFallback: DEFAULT_FONT_VARIANT_FALLBACK, lineHeight: 1, characterSpacing: 0, dynamicFontSize: void 0, fontColor: DEFAULT_FONT_COLOR, fontName: void 0, backgroundColor: "", borderColor: "#000000", borderWidth: createBoxDimension(0), padding: createBoxDimension(0), opacity: 1, strikethrough: false, underline: false } }; //#endregion //#region src/text/uiRender.ts var replaceUnsupportedChars = (text, fontKitFont) => { const charSupportCache = {}; const isCharSupported = (char) => { if (char in charSupportCache) return charSupportCache[char]; const isSupported = fontKitFont.hasGlyphForCodePoint(char.codePointAt(0) || 0); charSupportCache[char] = isSupported; return isSupported; }; return text.split(/(\r\n|\n|\r)/).map((segment) => { if (/\r\n|\n|\r/.test(segment)) return segment; return Array.from(segment).map((char) => { if (/\s/.test(char) || char.charCodeAt(0) < 32) return char; return isCharSupported(char) ? char : "〿"; }).join(""); }).join(""); }; var uiRender = async (arg) => { const { value, schema, basePdf, mode, onChange, stopEditing, tabIndex, placeholder, options, _cache } = arg; const hasInlineMarkdownFormat = schema.textFormat === TEXT_FORMAT_INLINE_MARKDOWN; const enableInlineMarkdown = isInlineMarkdownTextSchema(schema); const isReadOnlySplitInlineMarkdownFormChunk = mode === "form" && Boolean(getTextLineRange(schema)) && hasInlineMarkdownFormat; const renderInlineMarkdownReadOnlyChunk = enableInlineMarkdown || isReadOnlySplitInlineMarkdownFormChunk; const editable = isEditable(mode, schema) && !isReadOnlySplitInlineMarkdownFormChunk; const usePlaceholder = editable && placeholder && !value; const getText = (element) => { let text = element.innerText; if (text.endsWith("\n")) text = text.slice(0, -1); return text; }; const font = options?.font || getDefaultFont(); const fontKitFont = await getFontKitFont(schema.fontName, font, _cache); const enableDynamicFontSize = shouldUseDynamicFontSize(schema, basePdf); const displayValue = enableInlineMarkdown ? stripInlineMarkdown(value) : value; const dynamicRichTextFontSize = enableInlineMarkdown && enableDynamicFontSize ? await calculateDynamicRichTextFontSize({ value: usePlaceholder ? placeholder : value, schema, font, _cache }) : void 0; const textBlock = buildStyledTextContainer(isReadOnlySplitInlineMarkdownFormChunk ? { ...arg, mode: "viewer" } : arg, fontKitFont, usePlaceholder ? placeholder : displayValue, dynamicRichTextFontSize); const processedText = replaceUnsupportedChars(getRangedPlainTextValue({ value, schema, fontKitFont, fontSize: schema.fontSize ?? 13 }), fontKitFont); if (!editable) { if (renderInlineMarkdownReadOnlyChunk) { await renderInlineMarkdownReadOnly({ textBlock, value, schema, font, _cache }); return; } textBlock.innerHTML = processedText.split("").map((l, i) => { const escaped = l.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); return `<span style="letter-spacing:${String(value).length === i + 1 ? 0 : "inherit"};">${escaped}</span>`; }).join(""); return; } makeElementPlainTextContentEditable(textBlock); textBlock.tabIndex = tabIndex || 0; textBlock.innerText = mode === "designer" ? value : processedText; textBlock.addEventListener("blur", (e) => { if (onChange) onChange({ key: "content", value: getText(e.target) }); if (stopEditing) stopEditing(); }); if (enableDynamicFontSize) { let dynamicFontSize = void 0; textBlock.addEventListener("keyup", () => { setTimeout(() => { (() => { if (!textBlock.textContent) return; dynamicFontSize = calculateDynamicFontSize({ textSchema: schema, fontKitFont, value: isInlineMarkdownTextSchema(schema) ? stripInlineMarkdown(getText(textBlock)) : getText(textBlock), startingFontSize: dynamicFontSize }); textBlock.style.fontSize = `${dynamicFontSize}pt`; const { topAdj: newTopAdj, bottomAdj: newBottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? 13, schema.lineHeight ?? 1, schema.verticalAlignment ?? "top"); textBlock.style.paddingTop = `${newTopAdj}px`; textBlock.style.marginBottom = `${newBottomAdj}px`; })(); }, 0); }); } if (usePlaceholder) { textBlock.style.color = PLACEHOLDER_FONT_COLOR; textBlock.addEventListener("focus", () => { if (textBlock.innerText === placeholder) { textBlock.innerText = ""; textBlock.style.color = schema.fontColor ?? "#000000"; } }); } if (mode === "designer") setTimeout(() => { textBlock.focus(); const selection = window.getSelection(); const range = document.createRange(); if (selection && range) { range.selectNodeContents(textBlock); range.collapse(false); selection?.removeAllRanges(); selection?.addRange(range); } }); }; var renderInlineMarkdownReadOnly = async (arg) => { const { textBlock, value, schema, font, _cache } = arg; const runs = await resolveRichTextRuns({ runs: parseInlineMarkdown(value), schema, font, _cache }); const lineRange = getTextLineRange(schema); if (lineRange) { const lines = applyTextLineRange(layoutRichTextLines({ runs, fontSize: schema.fontSize ?? 13, characterSpacing: schema.characterSpacing ?? 0, boxWidthInPt: mm2pt(getBoxContentArea(schema).width) }), lineRange); textBlock.innerHTML = ""; lines.forEach((line, lineIndex) => { line.runs.forEach((run) => { appendInlineMarkdownRun({ textBlock, run, schema, font }); }); if (lineIndex < lines.length - 1) textBlock.appendChild(document.createElement("br")); }); return; } textBlock.innerHTML = ""; runs.forEach((run) => { appendInlineMarkdownRun({ textBlock, run, schema, font }); }); }; var appendInlineMarkdownRun = (arg) => { const { textBlock, run, schema, font } = arg; const href = run.href ? normalizeLinkHref(run.href) : void 0; const span = href ? document.createElement("a") : document.createElement("span"); const processedText = replaceUnsupportedChars(run.text, run.fontKitFont); const textDecorations = []; span.textContent = processedText; if (href) { const anchor = span; anchor.href = href; if (!getInternalLinkTarget(href)) { anchor.target = "_blank"; anchor.rel = "noopener noreferrer"; } textDecorations.push("underline"); } if (run.fontName) span.style.fontFamily = `'${run.fontName}'`; if (run.syntheticBold) { span.style.fontWeight = "800"; span.style.textShadow = SYNTHETIC_BOLD_CSS_TEXT_SHADOW; } if (run.syntheticItalic) span.style.fontStyle = "italic"; if (run.strikethrough) textDecorations.push("line-through"); if (textDecorations.length > 0) span.style.textDecoration = textDecorations.join(" "); if (run.code) { span.style.backgroundColor = CODE_BACKGROUND_COLOR; span.style.borderRadius = "2px"; span.style.padding = "0 0.15em"; if (!schema.fontVariants?.code || !font[schema.fontVariants.code]) span.style.fontFamily = run.fontName ? `'${run.fontName}', monospace` : "monospace"; } textBlock.appendChild(span); }; var getRangedPlainTextValue = (arg) => { const { value, schema, fontKitFont, fontSize } = arg; const lineRange = getTextLineRange(schema); if (!lineRange) return value; return plainTextLinesToValue(applyTextLineRange(splitTextToSize({ value, characterSpacing: schema.characterSpacing ?? 0, fontSize, fontKitFont, boxWidthInPt: mm2pt(getBoxContentArea(schema).width) }), lineRange)); }; var buildStyledTextContainer = (arg, fontKitFont, value, resolvedDynamicFontSize) => { const { schema, rootElement, mode } = arg; let dynamicFontSize = resolvedDynamicFontSize; if (dynamicFontSize === void 0 && shouldUseDynamicFontSize(schema, arg.basePdf) && value) dynamicFontSize = calculateDynamicFontSize({ textSchema: schema, fontKitFont, value, startingFontSize: dynamicFontSize }); const { topAdj, bottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? 13, schema.lineHeight ?? 1, schema.verticalAlignment ?? "top"); const topAdjustment = topAdj.toString(); const bottomAdjustment = bottomAdj.toString(); const verticalAlignment = schema.verticalAlignment ?? "top"; const isTopAligned = verticalAlignment === "top"; const container = document.createElement("div"); const { borderWidth, padding } = getBoxInsets(schema); const hasPadding = hasBoxDimension(schema.padding); const hasBorder = Boolean(schema.borderColor && hasBoxDimension(schema.borderWidth)); const containerStyle = { padding: hasPadding ? `${padding.top}mm ${padding.right}mm ${padding.bottom}mm ${padding.left}mm` : 0, resize: "none", backgroundColor: getBackgroundColor(schema), border: hasBorder ? void 0 : "none", ...hasBorder ? { borderTopWidth: `${borderWidth.top}mm`, borderRightWidth: `${borderWidth.right}mm`, borderBottomWidth: `${borderWidth.bottom}mm`, borderLeftWidth: `${borderWidth.left}mm`, borderStyle: "solid", borderColor: schema.borderColor } : {}, ...hasPadding || hasBorder ? { boxSizing: "border-box" } : {}, display: "flex", flexDirection: "column", justifyContent: mapVerticalAlignToFlex(verticalAlignment), width: "100%", height: "100%", cursor: isEditable(mode, schema) ? "text" : "default" }; Object.assign(container.style, containerStyle); rootElement.innerHTML = ""; rootElement.appendChild(container); const textDecorations = []; if (schema.strikethrough) textDecorations.push("line-through"); if (schema.underline) textDecorations.push("underline"); const textBlockStyle = { fontFamily: schema.fontName ? `'${schema.fontName}'` : "inherit", color: schema.fontColor ? schema.fontColor : DEFAULT_FONT_COLOR, fontSize: `${dynamicFontSize ?? schema.fontSize ?? 13}pt`, letterSpacing: `${schema.characterSpacing ?? 0}pt`, lineHeight: `${schema.lineHeight ?? 1}em`, textAlign: schema.alignment ?? "left", whiteSpace: "pre-wrap", wordBreak: "break-word", resize: "none", border: "none", outline: "none", marginBottom: `${bottomAdjustment}px`, paddingTop: `${topAdjustment}px`, backgroundColor: "transparent", textDecoration: textDecorations.join(" "), ...isTopAligned ? { height: "100%" } : {} }; const textBlock = document.createElement("div"); textBlock.id = "text-" + String(schema.id); Object.assign(textBlock.style, textBlockStyle); container.appendChild(textBlock); return textBlock; }; /** * Firefox doesn't support 'plaintext-only' contentEditable mode, which we want to avoid mark-up. * This function adds a workaround for Firefox to make the contentEditable element behave like 'plaintext-only'. */ var makeElementPlainTextContentEditable = (element) => { if (!isFirefox()) { element.contentEditable = "plaintext-only"; return; } element.contentEditable = "true"; element.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); document.execCommand("insertLineBreak", false, void 0); } }); element.addEventListener("paste", (e) => { e.preventDefault(); const paste = e.clipboardData?.getData("text"); const selection = window.getSelection(); if (!selection?.rangeCount) return; selection.deleteFromDocument(); selection.getRangeAt(0).insertNode(document.createTextNode(paste || "")); selection.collapseToEnd(); }); }; var mapVerticalAlignToFlex = (verticalAlignmentValue) => { switch (verticalAlignmentValue) { case "top": return "flex-start"; case VERTICAL_ALIGN_MIDDLE: return "center"; case VERTICAL_ALIGN_BOTTOM: return "flex-end"; } return "flex-start"; }; var getBackgroundColor = (schema) => { if (!schema.backgroundColor) return "transparent"; return schema.backgroundColor; }; //#endregion //#region src/text/index.ts var textSchema = { pdf: pdfRender, ui: uiRender, propPanel, icon: createSvgStr(TextCursorInput) }; //#endregion //#region src/builtins.ts var builtInPlugins = { Text: textSchema }; //#endregion export { mapVerticalAlignToFlex as a, Formatter as c, makeElementPlainTextContentEditable as i, getExtraFormatterSchema as l, textSchema as n, uiRender as o, buildStyledTextContainer as r, propPanel as s, builtInPlugins as t, pdfRender as u }; //# sourceMappingURL=builtins-BB2DHceW.js.map