react-pdf-highlighter-plus 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/{export-pdf-W2QGWADM.js → export-pdf-DD7J5UHW.js} +113 -15
- package/dist/esm/export-pdf-DD7J5UHW.js.map +1 -0
- package/dist/esm/index.d.ts +45 -8
- package/dist/esm/index.js +1553 -988
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pdf.worker.min.mjs +2 -2
- package/dist/esm/style/AreaHighlight.css +178 -73
- package/dist/esm/style/DrawingCanvas.css +24 -16
- package/dist/esm/style/DrawingHighlight.css +193 -61
- package/dist/esm/style/FreetextHighlight.css +217 -76
- package/dist/esm/style/ImageHighlight.css +98 -26
- package/dist/esm/style/MouseSelection.css +2 -2
- package/dist/esm/style/PdfHighlighter.css +29 -15
- package/dist/esm/style/ShapeCanvas.css +18 -12
- package/dist/esm/style/ShapeHighlight.css +142 -72
- package/dist/esm/style/SignaturePad.css +154 -38
- package/dist/esm/style/TextHighlight.css +205 -75
- package/dist/esm/style/style.css +1 -0
- package/dist/esm/style/tokens.css +88 -0
- package/package.json +32 -16
- package/dist/esm/export-pdf-W2QGWADM.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
|
2
|
+
|
|
1
3
|
// src/lib/export-pdf.ts
|
|
2
|
-
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
3
4
|
function parseColor(color) {
|
|
4
5
|
const rgbaMatch = color.match(
|
|
5
6
|
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
|
|
@@ -52,6 +53,82 @@ function dataUrlToBytes(dataUrl) {
|
|
|
52
53
|
const type = dataUrl.includes("image/png") ? "png" : "jpg";
|
|
53
54
|
return { bytes, type };
|
|
54
55
|
}
|
|
56
|
+
function dataUrlFormat(dataUrl) {
|
|
57
|
+
const mime = dataUrl.slice(5, dataUrl.indexOf(";")).toLowerCase();
|
|
58
|
+
if (mime === "image/png") return "png";
|
|
59
|
+
if (mime === "image/jpeg" || mime === "image/jpg") return "jpg";
|
|
60
|
+
return "other";
|
|
61
|
+
}
|
|
62
|
+
var CONTENT_RADIUS_PT = 9;
|
|
63
|
+
function roundedRectPath(ctx, w, h, r) {
|
|
64
|
+
const radius = Math.max(0, Math.min(r, w / 2, h / 2));
|
|
65
|
+
ctx.beginPath();
|
|
66
|
+
ctx.moveTo(radius, 0);
|
|
67
|
+
ctx.arcTo(w, 0, w, h, radius);
|
|
68
|
+
ctx.arcTo(w, h, 0, h, radius);
|
|
69
|
+
ctx.arcTo(0, h, 0, 0, radius);
|
|
70
|
+
ctx.arcTo(0, 0, w, 0, radius);
|
|
71
|
+
ctx.closePath();
|
|
72
|
+
}
|
|
73
|
+
async function compositeHighlightImageToPng(dataUrl, boxWidthPts, boxHeightPts, opts) {
|
|
74
|
+
if (typeof document === "undefined" || typeof Image === "undefined") {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const img = await new Promise((resolve) => {
|
|
78
|
+
const image = new Image();
|
|
79
|
+
image.onload = () => resolve(image);
|
|
80
|
+
image.onerror = () => resolve(null);
|
|
81
|
+
image.src = dataUrl;
|
|
82
|
+
});
|
|
83
|
+
if (!img) return null;
|
|
84
|
+
const scale = 2;
|
|
85
|
+
const cw = Math.max(1, Math.round(boxWidthPts * scale));
|
|
86
|
+
const ch = Math.max(1, Math.round(boxHeightPts * scale));
|
|
87
|
+
const canvas = document.createElement("canvas");
|
|
88
|
+
canvas.width = cw;
|
|
89
|
+
canvas.height = ch;
|
|
90
|
+
const ctx = canvas.getContext("2d");
|
|
91
|
+
if (!ctx) return null;
|
|
92
|
+
roundedRectPath(ctx, cw, ch, CONTENT_RADIUS_PT * scale);
|
|
93
|
+
ctx.clip();
|
|
94
|
+
if (opts.background) {
|
|
95
|
+
ctx.fillStyle = opts.background;
|
|
96
|
+
ctx.fillRect(0, 0, cw, ch);
|
|
97
|
+
}
|
|
98
|
+
const iw = img.naturalWidth || cw;
|
|
99
|
+
const ih = img.naturalHeight || ch;
|
|
100
|
+
if (opts.fit === "contain") {
|
|
101
|
+
const s = Math.min(cw / iw, ch / ih);
|
|
102
|
+
const dw = iw * s;
|
|
103
|
+
const dh = ih * s;
|
|
104
|
+
ctx.drawImage(img, (cw - dw) / 2, (ch - dh) / 2, dw, dh);
|
|
105
|
+
} else {
|
|
106
|
+
ctx.drawImage(img, 0, 0, cw, ch);
|
|
107
|
+
}
|
|
108
|
+
return dataUrlToBytes(canvas.toDataURL("image/png")).bytes;
|
|
109
|
+
}
|
|
110
|
+
function drawRoundedRectangle(page, o) {
|
|
111
|
+
const r = Math.max(0, Math.min(o.radius, o.width / 2, o.height / 2));
|
|
112
|
+
if (r <= 0) {
|
|
113
|
+
page.drawRectangle({
|
|
114
|
+
x: o.x,
|
|
115
|
+
y: o.y,
|
|
116
|
+
width: o.width,
|
|
117
|
+
height: o.height,
|
|
118
|
+
color: o.color,
|
|
119
|
+
opacity: o.opacity
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const { width: w, height: h } = o;
|
|
124
|
+
const path = `M ${r} 0 H ${w - r} A ${r} ${r} 0 0 1 ${w} ${r} V ${h - r} A ${r} ${r} 0 0 1 ${w - r} ${h} H ${r} A ${r} ${r} 0 0 1 0 ${h - r} V ${r} A ${r} ${r} 0 0 1 ${r} 0 Z`;
|
|
125
|
+
page.drawSvgPath(path, {
|
|
126
|
+
x: o.x,
|
|
127
|
+
y: o.y + h,
|
|
128
|
+
color: o.color,
|
|
129
|
+
opacity: o.opacity
|
|
130
|
+
});
|
|
131
|
+
}
|
|
55
132
|
function wrapText(text, font, fontSize, maxWidth) {
|
|
56
133
|
if (!text || maxWidth <= 0) return [];
|
|
57
134
|
const lines = [];
|
|
@@ -114,13 +191,14 @@ async function renderTextHighlight(page, highlight, options) {
|
|
|
114
191
|
for (const rect of rects) {
|
|
115
192
|
const { x, y, width, height } = scaledToPdfPoints(rect, page);
|
|
116
193
|
if (highlightStyle === "highlight") {
|
|
117
|
-
page
|
|
194
|
+
drawRoundedRectangle(page, {
|
|
118
195
|
x,
|
|
119
196
|
y,
|
|
120
197
|
width,
|
|
121
198
|
height,
|
|
122
199
|
color: rgb(color.r, color.g, color.b),
|
|
123
|
-
opacity: color.a
|
|
200
|
+
opacity: color.a,
|
|
201
|
+
radius: CONTENT_RADIUS_PT
|
|
124
202
|
});
|
|
125
203
|
} else if (highlightStyle === "underline") {
|
|
126
204
|
const lineThickness = Math.max(1, height * 0.1);
|
|
@@ -153,13 +231,14 @@ async function renderAreaHighlight(page, highlight, options) {
|
|
|
153
231
|
highlight.position.boundingRect,
|
|
154
232
|
page
|
|
155
233
|
);
|
|
156
|
-
page
|
|
234
|
+
drawRoundedRectangle(page, {
|
|
157
235
|
x,
|
|
158
236
|
y,
|
|
159
237
|
width,
|
|
160
238
|
height,
|
|
161
239
|
color: rgb(color.r, color.g, color.b),
|
|
162
|
-
opacity: color.a
|
|
240
|
+
opacity: color.a,
|
|
241
|
+
radius: CONTENT_RADIUS_PT
|
|
163
242
|
});
|
|
164
243
|
}
|
|
165
244
|
async function renderFreetextHighlight(page, highlight, options, font) {
|
|
@@ -185,13 +264,14 @@ async function renderFreetextHighlight(page, highlight, options, font) {
|
|
|
185
264
|
const bgColorValue = highlight.backgroundColor || options.defaultFreetextBgColor || "#ffffc8";
|
|
186
265
|
if (bgColorValue !== "transparent") {
|
|
187
266
|
const bgColor = parseColor(bgColorValue);
|
|
188
|
-
page
|
|
267
|
+
drawRoundedRectangle(page, {
|
|
189
268
|
x,
|
|
190
269
|
y,
|
|
191
270
|
width,
|
|
192
271
|
height,
|
|
193
272
|
color: rgb(bgColor.r, bgColor.g, bgColor.b),
|
|
194
|
-
opacity: bgColor.a
|
|
273
|
+
opacity: bgColor.a,
|
|
274
|
+
radius: CONTENT_RADIUS_PT
|
|
195
275
|
});
|
|
196
276
|
}
|
|
197
277
|
const padding = 4 * yRatio;
|
|
@@ -243,16 +323,34 @@ function transformToRawCoordinates(page, x, y, width, height) {
|
|
|
243
323
|
}
|
|
244
324
|
return { x, y, width, height };
|
|
245
325
|
}
|
|
246
|
-
async function renderImageHighlight(pdfDoc, page, highlight) {
|
|
326
|
+
async function renderImageHighlight(pdfDoc, page, highlight, kind = "image") {
|
|
247
327
|
const imageDataUrl = highlight.content?.image;
|
|
248
328
|
if (!imageDataUrl) return;
|
|
249
329
|
try {
|
|
250
|
-
const { bytes, type } = dataUrlToBytes(imageDataUrl);
|
|
251
|
-
const image = type === "png" ? await pdfDoc.embedPng(bytes) : await pdfDoc.embedJpg(bytes);
|
|
252
330
|
const visualCoords = scaledToPdfPoints(
|
|
253
331
|
highlight.position.boundingRect,
|
|
254
332
|
page
|
|
255
333
|
);
|
|
334
|
+
let image;
|
|
335
|
+
const pngBytes = await compositeHighlightImageToPng(
|
|
336
|
+
imageDataUrl,
|
|
337
|
+
visualCoords.width,
|
|
338
|
+
visualCoords.height,
|
|
339
|
+
{ fit: kind === "drawing" ? "contain" : "fill" }
|
|
340
|
+
);
|
|
341
|
+
if (pngBytes) {
|
|
342
|
+
image = await pdfDoc.embedPng(pngBytes);
|
|
343
|
+
} else {
|
|
344
|
+
const format = dataUrlFormat(imageDataUrl);
|
|
345
|
+
if (format === "png") {
|
|
346
|
+
image = await pdfDoc.embedPng(dataUrlToBytes(imageDataUrl).bytes);
|
|
347
|
+
} else if (format === "jpg") {
|
|
348
|
+
image = await pdfDoc.embedJpg(dataUrlToBytes(imageDataUrl).bytes);
|
|
349
|
+
} else {
|
|
350
|
+
console.error("Cannot embed image: unsupported format and no canvas to rasterize");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
256
354
|
const rawCoords = transformToRawCoordinates(
|
|
257
355
|
page,
|
|
258
356
|
visualCoords.x,
|
|
@@ -383,7 +481,7 @@ async function exportPdf(pdfSource, highlights, options = {}) {
|
|
|
383
481
|
await renderImageHighlight(pdfDoc, page, highlight);
|
|
384
482
|
break;
|
|
385
483
|
case "drawing":
|
|
386
|
-
await renderImageHighlight(pdfDoc, page, highlight);
|
|
484
|
+
await renderImageHighlight(pdfDoc, page, highlight, "drawing");
|
|
387
485
|
break;
|
|
388
486
|
case "shape":
|
|
389
487
|
await renderShapeHighlight(page, highlight);
|
|
@@ -397,7 +495,7 @@ async function exportPdf(pdfSource, highlights, options = {}) {
|
|
|
397
495
|
}
|
|
398
496
|
return pdfDoc.save();
|
|
399
497
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
//# sourceMappingURL=export-pdf-
|
|
498
|
+
|
|
499
|
+
export { exportPdf };
|
|
500
|
+
//# sourceMappingURL=export-pdf-DD7J5UHW.js.map
|
|
501
|
+
//# sourceMappingURL=export-pdf-DD7J5UHW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/export-pdf.ts"],"names":[],"mappings":"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","file":"export-pdf-DD7J5UHW.js","sourcesContent":["import { PDFDocument, rgb, StandardFonts, PDFPage, PDFFont } from \"pdf-lib\";\nimport type { Scaled, ScaledPosition, ShapeData } from \"../types\";\n\n/**\n * Options for the PDF export function.\n *\n * @category Type\n */\nexport interface ExportPdfOptions {\n /** Default color for text highlights. Default: \"rgba(255, 226, 143, 0.5)\" */\n textHighlightColor?: string;\n /** Default color for area highlights. Default: \"rgba(255, 226, 143, 0.5)\" */\n areaHighlightColor?: string;\n /** Default text color for freetext. Default: \"#333333\" */\n defaultFreetextColor?: string;\n /** Default background for freetext. Default: \"#ffffc8\" */\n defaultFreetextBgColor?: string;\n /** Default font size for freetext. Default: 14 */\n defaultFreetextFontSize?: number;\n /** Progress callback for large PDFs */\n onProgress?: (current: number, total: number) => void;\n}\n\n/**\n * A highlight that can be exported to PDF.\n *\n * @category Type\n */\nexport interface ExportableHighlight {\n id: string;\n type?: \"text\" | \"area\" | \"freetext\" | \"image\" | \"drawing\" | \"shape\";\n content?: {\n text?: string;\n image?: string; // Base64 data URL\n shape?: ShapeData; // Shape data for shape highlights\n };\n position: ScaledPosition;\n /** Per-highlight color override (for text/area highlights) */\n highlightColor?: string;\n /** Style mode for text highlights: \"highlight\" (default), \"underline\", or \"strikethrough\" */\n highlightStyle?: \"highlight\" | \"underline\" | \"strikethrough\";\n /** Text color for freetext highlights */\n color?: string;\n /** Background color for freetext highlights */\n backgroundColor?: string;\n /** Font size for freetext highlights */\n fontSize?: string;\n /** Font family for freetext highlights (not used in export, Helvetica is always used) */\n fontFamily?: string;\n /** Shape type for shape highlights */\n shapeType?: \"rectangle\" | \"circle\" | \"arrow\";\n /** Stroke color for shape highlights */\n strokeColor?: string;\n /** Stroke width for shape highlights */\n strokeWidth?: number;\n}\n\n/**\n * Parse a color string to RGB values (0-1 range).\n */\nfunction parseColor(color: string): {\n r: number;\n g: number;\n b: number;\n a: number;\n} {\n // Handle rgba(r, g, b, a) and rgb(r, g, b)\n const rgbaMatch = color.match(\n /rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/\n );\n if (rgbaMatch) {\n return {\n r: parseInt(rgbaMatch[1]) / 255,\n g: parseInt(rgbaMatch[2]) / 255,\n b: parseInt(rgbaMatch[3]) / 255,\n a: rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1,\n };\n }\n\n // Handle hex (#RRGGBB or #RGB)\n const hex = color.replace(\"#\", \"\");\n if (hex.length === 3) {\n return {\n r: parseInt(hex[0] + hex[0], 16) / 255,\n g: parseInt(hex[1] + hex[1], 16) / 255,\n b: parseInt(hex[2] + hex[2], 16) / 255,\n a: 1,\n };\n }\n if (hex.length === 6) {\n return {\n r: parseInt(hex.slice(0, 2), 16) / 255,\n g: parseInt(hex.slice(2, 4), 16) / 255,\n b: parseInt(hex.slice(4, 6), 16) / 255,\n a: 1,\n };\n }\n\n // Default yellow\n return { r: 1, g: 0.89, b: 0.56, a: 0.5 };\n}\n\n/**\n * Convert ScaledPosition coordinates to PDF points.\n * PDF coordinate system has origin at bottom-left.\n */\nfunction scaledToPdfPoints(\n scaled: Scaled,\n page: PDFPage\n): { x: number; y: number; width: number; height: number } {\n const pdfWidth = page.getWidth();\n const pdfHeight = page.getHeight();\n\n // Calculate position ratios\n const xRatio = pdfWidth / scaled.width;\n const yRatio = pdfHeight / scaled.height;\n\n const x = scaled.x1 * xRatio;\n const width = (scaled.x2 - scaled.x1) * xRatio;\n const height = (scaled.y2 - scaled.y1) * yRatio;\n\n // Flip Y (PDF origin is bottom-left, screen origin is top-left)\n const y = pdfHeight - scaled.y1 * yRatio - height;\n\n return { x, y, width, height };\n}\n\n/**\n * Convert base64 data URL to bytes.\n */\nfunction dataUrlToBytes(dataUrl: string): {\n bytes: Uint8Array;\n type: \"png\" | \"jpg\";\n} {\n const base64 = dataUrl.split(\",\")[1];\n const byteString = atob(base64);\n const bytes = new Uint8Array(byteString.length);\n for (let i = 0; i < byteString.length; i++) {\n bytes[i] = byteString.charCodeAt(i);\n }\n const type = dataUrl.includes(\"image/png\") ? \"png\" : \"jpg\";\n return { bytes, type };\n}\n\n/**\n * Detect the encoded image format from a data URL's MIME type.\n * pdf-lib can embed PNG and JPEG directly; anything else (notably SVG) must be\n * rasterized to PNG first.\n */\nfunction dataUrlFormat(dataUrl: string): \"png\" | \"jpg\" | \"other\" {\n const mime = dataUrl.slice(5, dataUrl.indexOf(\";\")).toLowerCase();\n if (mime === \"image/png\") return \"png\";\n if (mime === \"image/jpeg\" || mime === \"image/jpg\") return \"jpg\";\n return \"other\";\n}\n\n/**\n * Corner radius (in PDF points) baked into exported image/drawing highlights so\n * they match the rounded `--rphp-radius: 9px` box used in the on-screen preview.\n */\nconst CONTENT_RADIUS_PT = 9;\n\n/** Trace a rounded-rectangle subpath (fallback for browsers without ctx.roundRect). */\nfunction roundedRectPath(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n r: number,\n): void {\n const radius = Math.max(0, Math.min(r, w / 2, h / 2));\n ctx.beginPath();\n ctx.moveTo(radius, 0);\n ctx.arcTo(w, 0, w, h, radius);\n ctx.arcTo(w, h, 0, h, radius);\n ctx.arcTo(0, h, 0, 0, radius);\n ctx.arcTo(0, 0, w, 0, radius);\n ctx.closePath();\n}\n\n/**\n * Composite an image/drawing highlight onto an offscreen canvas exactly as the\n * preview renders it, then return PNG bytes for pdf-lib to embed. This does\n * three things the old raw-embed path couldn't:\n *\n * 1. Handles SVG/WebP/etc. — the previous code blindly fed non-PNG to embedJpg,\n * failing with \"SOI not found in JPEG\" and dropping the image.\n * 2. Bakes in the rounded corners (`--rphp-radius`) so export matches preview.\n * 3. Applies the correct object-fit — `fill` for images (stretch to box),\n * `contain` for drawings (keep aspect, centered on a white backing) — so a\n * drawing whose aspect differs from its box is no longer distorted.\n *\n * The canvas is sized to the box's aspect ratio, so the caller draws it filling\n * the box (rotation handling unchanged). Requires a DOM; returns null\n * server-side so the caller can fall back to a direct embed.\n *\n * @param boxWidthPts / boxHeightPts - Box size in PDF points, used for aspect\n * ratio, raster resolution, and radius scaling.\n */\nasync function compositeHighlightImageToPng(\n dataUrl: string,\n boxWidthPts: number,\n boxHeightPts: number,\n opts: { fit: \"fill\" | \"contain\"; background?: string },\n): Promise<Uint8Array | null> {\n if (typeof document === \"undefined\" || typeof Image === \"undefined\") {\n return null;\n }\n\n const img = await new Promise<HTMLImageElement | null>((resolve) => {\n const image = new Image();\n image.onload = () => resolve(image);\n image.onerror = () => resolve(null);\n image.src = dataUrl;\n });\n if (!img) return null;\n\n // Render at 2× the box size (in points) so the baked-in result stays crisp\n // when embedded and scaled back to the box.\n const scale = 2;\n const cw = Math.max(1, Math.round(boxWidthPts * scale));\n const ch = Math.max(1, Math.round(boxHeightPts * scale));\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = cw;\n canvas.height = ch;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return null;\n\n // Clip everything to the rounded box so corners are transparent (page shows\n // through), matching the preview's `overflow: hidden` rounded content box.\n roundedRectPath(ctx, cw, ch, CONTENT_RADIUS_PT * scale);\n ctx.clip();\n\n if (opts.background) {\n ctx.fillStyle = opts.background;\n ctx.fillRect(0, 0, cw, ch);\n }\n\n const iw = img.naturalWidth || cw;\n const ih = img.naturalHeight || ch;\n\n if (opts.fit === \"contain\") {\n const s = Math.min(cw / iw, ch / ih);\n const dw = iw * s;\n const dh = ih * s;\n ctx.drawImage(img, (cw - dw) / 2, (ch - dh) / 2, dw, dh);\n } else {\n ctx.drawImage(img, 0, 0, cw, ch);\n }\n\n return dataUrlToBytes(canvas.toDataURL(\"image/png\")).bytes;\n}\n\n/**\n * Draw a filled rounded rectangle. pdf-lib has no rounded-rect primitive, so\n * this builds an SVG path and draws it via drawSvgPath. Matches the preview's\n * `--rphp-radius: 9px` rounded highlight/area/freetext boxes, which the old\n * drawRectangle calls rendered with sharp corners.\n *\n * @param x, y - Bottom-left of the box in PDF points (Y up, as elsewhere here).\n */\nfunction drawRoundedRectangle(\n page: PDFPage,\n o: {\n x: number;\n y: number;\n width: number;\n height: number;\n color: ReturnType<typeof rgb>;\n opacity: number;\n radius: number;\n }\n): void {\n const r = Math.max(0, Math.min(o.radius, o.width / 2, o.height / 2));\n if (r <= 0) {\n page.drawRectangle({\n x: o.x,\n y: o.y,\n width: o.width,\n height: o.height,\n color: o.color,\n opacity: o.opacity,\n });\n return;\n }\n const { width: w, height: h } = o;\n // Path is in SVG space (origin top-left, Y down). drawSvgPath anchors SVG\n // (0,0) at the given (x, y), so pass the box's TOP edge (o.y + h) as y.\n const path =\n `M ${r} 0 H ${w - r} A ${r} ${r} 0 0 1 ${w} ${r} ` +\n `V ${h - r} A ${r} ${r} 0 0 1 ${w - r} ${h} ` +\n `H ${r} A ${r} ${r} 0 0 1 0 ${h - r} ` +\n `V ${r} A ${r} ${r} 0 0 1 ${r} 0 Z`;\n page.drawSvgPath(path, {\n x: o.x,\n y: o.y + h,\n color: o.color,\n opacity: o.opacity,\n });\n}\n\n/**\n * Wrap text into multiple lines that fit within maxWidth.\n * Long words are broken character by character (like CSS word-wrap: break-word).\n */\nfunction wrapText(\n text: string,\n font: PDFFont,\n fontSize: number,\n maxWidth: number\n): string[] {\n if (!text || maxWidth <= 0) return [];\n\n const lines: string[] = [];\n\n // Split by newlines first to preserve intentional line breaks\n const paragraphs = text.split(/\\n/);\n\n for (const paragraph of paragraphs) {\n if (!paragraph.trim()) {\n lines.push(\"\");\n continue;\n }\n\n const words = paragraph.split(/\\s+/);\n let currentLine = \"\";\n\n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word;\n const testWidth = font.widthOfTextAtSize(testLine, fontSize);\n\n if (testWidth <= maxWidth) {\n currentLine = testLine;\n } else {\n // Push current line if exists\n if (currentLine) {\n lines.push(currentLine);\n currentLine = \"\";\n }\n\n // Check if word itself is too wide - break it character by character\n if (font.widthOfTextAtSize(word, fontSize) > maxWidth) {\n let remaining = word;\n while (remaining.length > 0) {\n let charCount = 1;\n // Find how many characters fit in maxWidth\n while (\n charCount < remaining.length &&\n font.widthOfTextAtSize(remaining.substring(0, charCount + 1), fontSize) <= maxWidth\n ) {\n charCount++;\n }\n const chunk = remaining.substring(0, charCount);\n remaining = remaining.substring(charCount);\n\n if (remaining.length > 0) {\n // More characters remaining, push this chunk as a complete line\n lines.push(chunk);\n } else {\n // Last chunk, keep it as current line (may combine with next word)\n currentLine = chunk;\n }\n }\n } else {\n currentLine = word;\n }\n }\n }\n if (currentLine) lines.push(currentLine);\n }\n\n return lines;\n}\n\n/**\n * Group highlights by page number.\n */\nfunction groupByPage(\n highlights: ExportableHighlight[]\n): Map<number, ExportableHighlight[]> {\n const map = new Map<number, ExportableHighlight[]>();\n for (const h of highlights) {\n const pageNum = h.position.boundingRect.pageNumber;\n if (!map.has(pageNum)) map.set(pageNum, []);\n map.get(pageNum)!.push(h);\n }\n return map;\n}\n\n/**\n * Render a text highlight (multiple rectangles for multi-line selections).\n * Supports highlight (background), underline, and strikethrough styles.\n */\nasync function renderTextHighlight(\n page: PDFPage,\n highlight: ExportableHighlight,\n options: ExportPdfOptions\n): Promise<void> {\n // Per-highlight color override or fallback to default\n const colorStr =\n highlight.highlightColor ||\n options.textHighlightColor ||\n \"rgba(255, 226, 143, 0.5)\";\n const color = parseColor(colorStr);\n const highlightStyle = highlight.highlightStyle || \"highlight\";\n\n // Text highlights use rects array for multi-line selections\n const rects =\n highlight.position.rects.length > 0\n ? highlight.position.rects\n : [highlight.position.boundingRect];\n\n for (const rect of rects) {\n const { x, y, width, height } = scaledToPdfPoints(rect, page);\n\n if (highlightStyle === \"highlight\") {\n // Draw filled rounded rectangle for background highlight (rounded to\n // match the preview's `--rphp-radius` highlight parts; clamps to a pill\n // on short rects).\n drawRoundedRectangle(page, {\n x,\n y,\n width,\n height,\n color: rgb(color.r, color.g, color.b),\n opacity: color.a,\n radius: CONTENT_RADIUS_PT,\n });\n } else if (highlightStyle === \"underline\") {\n // Draw line at bottom of rectangle\n const lineThickness = Math.max(1, height * 0.1);\n page.drawRectangle({\n x,\n y,\n width,\n height: lineThickness,\n color: rgb(color.r, color.g, color.b),\n opacity: color.a,\n });\n } else if (highlightStyle === \"strikethrough\") {\n // Draw line through middle of rectangle\n const lineThickness = Math.max(1, height * 0.1);\n const lineY = y + height / 2 - lineThickness / 2;\n page.drawRectangle({\n x,\n y: lineY,\n width,\n height: lineThickness,\n color: rgb(color.r, color.g, color.b),\n opacity: color.a,\n });\n }\n }\n}\n\n/**\n * Render an area highlight (single rectangle).\n */\nasync function renderAreaHighlight(\n page: PDFPage,\n highlight: ExportableHighlight,\n options: ExportPdfOptions\n): Promise<void> {\n // Per-highlight color override or fallback to default\n const colorStr =\n highlight.highlightColor ||\n options.areaHighlightColor ||\n \"rgba(255, 226, 143, 0.5)\";\n const color = parseColor(colorStr);\n const { x, y, width, height } = scaledToPdfPoints(\n highlight.position.boundingRect,\n page\n );\n\n drawRoundedRectangle(page, {\n x,\n y,\n width,\n height,\n color: rgb(color.r, color.g, color.b),\n opacity: color.a,\n radius: CONTENT_RADIUS_PT,\n });\n}\n\n/**\n * Render a freetext highlight (background rectangle + text).\n * Text is wrapped to fit within the box.\n */\nasync function renderFreetextHighlight(\n page: PDFPage,\n highlight: ExportableHighlight,\n options: ExportPdfOptions,\n font: PDFFont\n): Promise<void> {\n const text = highlight.content?.text || \"\";\n const textColor = parseColor(\n highlight.color || options.defaultFreetextColor || \"#333333\"\n );\n\n // Get box dimensions in PDF points\n const { x, y, width, height } = scaledToPdfPoints(\n highlight.position.boundingRect,\n page\n );\n\n // Scale font size by the same ratio used for the box coordinates\n // This ensures the font scales proportionally with the box\n const pdfHeight = page.getHeight();\n const yRatio = pdfHeight / highlight.position.boundingRect.height;\n const storedFontSize =\n parseInt(highlight.fontSize || \"\") || options.defaultFreetextFontSize || 14;\n const fontSize = storedFontSize * yRatio;\n\n console.log(\"Freetext export:\", {\n storedFontSize,\n yRatio,\n fontSize,\n boxDimensions: { x, y, width, height },\n text: text.substring(0, 50),\n });\n\n // Draw background (skip if transparent)\n const bgColorValue = highlight.backgroundColor || options.defaultFreetextBgColor || \"#ffffc8\";\n if (bgColorValue !== \"transparent\") {\n const bgColor = parseColor(bgColorValue);\n drawRoundedRectangle(page, {\n x,\n y,\n width,\n height,\n color: rgb(bgColor.r, bgColor.g, bgColor.b),\n opacity: bgColor.a,\n radius: CONTENT_RADIUS_PT,\n });\n }\n\n // Draw wrapped text with scaled padding\n const padding = 4 * yRatio;\n const maxWidth = width - padding * 2;\n const lineHeight = fontSize * 1.3;\n\n if (maxWidth > 0 && text) {\n const lines = wrapText(text, font, fontSize, maxWidth);\n let currentY = y + height - fontSize - padding;\n\n for (const line of lines) {\n // Stop if we've run out of vertical space\n if (currentY < y + padding) break;\n\n // Skip empty lines but still move down\n if (line.trim()) {\n page.drawText(line, {\n x: x + padding,\n y: currentY,\n size: fontSize,\n font,\n color: rgb(textColor.r, textColor.g, textColor.b),\n });\n }\n\n currentY -= lineHeight;\n }\n }\n}\n\n/**\n * Transform visual coordinates to raw MediaBox coordinates.\n * pdf-lib's drawImage uses raw MediaBox space, but our coordinates are in visual space.\n */\nfunction transformToRawCoordinates(\n page: PDFPage,\n x: number,\n y: number,\n width: number,\n height: number\n): { x: number; y: number; width: number; height: number } {\n const rotation = page.getRotation().angle;\n const pageWidth = page.getWidth(); // Visual width\n const pageHeight = page.getHeight(); // Visual height\n\n if (rotation === 90) {\n // Visual (x, y) → Raw MediaBox coordinates\n // When rotated 90° CCW, visual top-left maps to raw bottom-left\n return {\n x: y,\n y: pageWidth - x - width,\n width: height,\n height: width,\n };\n } else if (rotation === 180) {\n // Rotated 180°, origin flips to opposite corner\n return {\n x: pageWidth - x - width,\n y: pageHeight - y - height,\n width,\n height,\n };\n } else if (rotation === 270) {\n // When rotated 90° CW (270° CCW)\n return {\n x: pageHeight - y - height,\n y: x,\n width: height,\n height: width,\n };\n }\n\n // No rotation - coordinates are already correct\n return { x, y, width, height };\n}\n\n/**\n * Render an image or drawing highlight (embedded image).\n * Handles page rotation by transforming visual coordinates to raw MediaBox space.\n * The image is composited to match the preview's rounded, white-backed content\n * box before embedding, so corners, backing, and aspect-fit all line up.\n *\n * @param kind - \"image\" fills the box (object-fit: fill, like a photo tile);\n * \"drawing\" fits inside keeping aspect (object-fit: contain) on a white\n * backing, matching each type's CSS in the preview.\n */\nasync function renderImageHighlight(\n pdfDoc: PDFDocument,\n page: PDFPage,\n highlight: ExportableHighlight,\n kind: \"image\" | \"drawing\" = \"image\"\n): Promise<void> {\n const imageDataUrl = highlight.content?.image;\n if (!imageDataUrl) return;\n\n try {\n // Calculate coordinates in visual space - use full bounding box dimensions\n const visualCoords = scaledToPdfPoints(\n highlight.position.boundingRect,\n page\n );\n\n // Composite to a PNG that bakes in the preview's rounded corners and\n // per-type object-fit. No background fill: signatures and drawings are\n // transparent-PNG ink, so they read as ink-only over the page — matching\n // shape highlights (rectangle/arrow), which are stroke-only on a\n // transparent background. Opaque photos still cover their box on their own.\n // Falls back to a direct embed when no canvas is available (e.g.\n // server-side) — sharp corners, but never drops the image (and still\n // rasterizes SVG, which pdf-lib can't embed itself).\n let image;\n const pngBytes = await compositeHighlightImageToPng(\n imageDataUrl,\n visualCoords.width,\n visualCoords.height,\n { fit: kind === \"drawing\" ? \"contain\" : \"fill\" }\n );\n if (pngBytes) {\n image = await pdfDoc.embedPng(pngBytes);\n } else {\n const format = dataUrlFormat(imageDataUrl);\n if (format === \"png\") {\n image = await pdfDoc.embedPng(dataUrlToBytes(imageDataUrl).bytes);\n } else if (format === \"jpg\") {\n image = await pdfDoc.embedJpg(dataUrlToBytes(imageDataUrl).bytes);\n } else {\n console.error(\"Cannot embed image: unsupported format and no canvas to rasterize\");\n return;\n }\n }\n\n // Transform to raw MediaBox coordinates based on page rotation\n const rawCoords = transformToRawCoordinates(\n page,\n visualCoords.x,\n visualCoords.y,\n visualCoords.width,\n visualCoords.height\n );\n\n console.log(\"Image export:\", {\n rotation: page.getRotation().angle,\n visualCoords,\n rawCoords,\n });\n\n // Draw image filling the entire bounding box\n page.drawImage(image, {\n x: rawCoords.x,\n y: rawCoords.y,\n width: rawCoords.width,\n height: rawCoords.height,\n });\n } catch (error) {\n console.error(\"Failed to embed image:\", error);\n }\n}\n\n/**\n * Render a shape highlight (rectangle, circle, or arrow).\n */\nasync function renderShapeHighlight(\n page: PDFPage,\n highlight: ExportableHighlight\n): Promise<void> {\n // Get shape data from content or top-level properties\n const shapeType = highlight.content?.shape?.shapeType || highlight.shapeType || \"rectangle\";\n const strokeColorStr = highlight.content?.shape?.strokeColor || highlight.strokeColor || \"#000000\";\n const strokeWidth = highlight.content?.shape?.strokeWidth || highlight.strokeWidth || 2;\n\n const color = parseColor(strokeColorStr);\n const { x, y, width, height } = scaledToPdfPoints(\n highlight.position.boundingRect,\n page\n );\n\n switch (shapeType) {\n case \"rectangle\":\n page.drawRectangle({\n x,\n y,\n width,\n height,\n borderColor: rgb(color.r, color.g, color.b),\n borderWidth: strokeWidth,\n opacity: color.a,\n });\n break;\n\n case \"circle\":\n page.drawEllipse({\n x: x + width / 2,\n y: y + height / 2,\n xScale: width / 2,\n yScale: height / 2,\n borderColor: rgb(color.r, color.g, color.b),\n borderWidth: strokeWidth,\n opacity: color.a,\n });\n break;\n\n case \"arrow\": {\n // Use stored start/end points if available, otherwise default to left-to-right\n const startPt = highlight.content?.shape?.startPoint;\n const endPt = highlight.content?.shape?.endPoint;\n\n // Calculate actual coordinates\n // Note: PDF coordinates have Y going up, so we need to flip the Y\n const startX = startPt ? x + startPt.x * width : x;\n const startY = startPt ? y + (1 - startPt.y) * height : y + height / 2;\n const endX = endPt ? x + endPt.x * width : x + width;\n const endY = endPt ? y + (1 - endPt.y) * height : y + height / 2;\n\n // Draw the main line\n page.drawLine({\n start: { x: startX, y: startY },\n end: { x: endX, y: endY },\n color: rgb(color.r, color.g, color.b),\n thickness: strokeWidth,\n opacity: color.a,\n });\n\n // Calculate arrowhead direction\n const angle = Math.atan2(endY - startY, endX - startX);\n const arrowSize = Math.min(15, width * 0.2, height * 0.4);\n const arrowAngle = Math.PI / 6; // 30 degrees\n\n // Draw arrowhead (two lines forming a V at the end)\n page.drawLine({\n start: {\n x: endX - arrowSize * Math.cos(angle - arrowAngle),\n y: endY - arrowSize * Math.sin(angle - arrowAngle),\n },\n end: { x: endX, y: endY },\n color: rgb(color.r, color.g, color.b),\n thickness: strokeWidth,\n opacity: color.a,\n });\n page.drawLine({\n start: {\n x: endX - arrowSize * Math.cos(angle + arrowAngle),\n y: endY - arrowSize * Math.sin(angle + arrowAngle),\n },\n end: { x: endX, y: endY },\n color: rgb(color.r, color.g, color.b),\n thickness: strokeWidth,\n opacity: color.a,\n });\n break;\n }\n }\n}\n\n/**\n * Export a PDF with annotations embedded.\n *\n * @param pdfSource - The source PDF as a URL string, Uint8Array, or ArrayBuffer\n * @param highlights - Array of highlights to embed in the PDF\n * @param options - Export options for customizing colors and behavior\n * @returns Promise<Uint8Array> - The modified PDF as bytes\n *\n * @example\n * ```typescript\n * const pdfBytes = await exportPdf(pdfUrl, highlights, {\n * textHighlightColor: \"rgba(255, 255, 0, 0.4)\",\n * onProgress: (current, total) => console.log(`${current}/${total} pages`)\n * });\n *\n * // Download the file\n * const blob = new Blob([pdfBytes], { type: \"application/pdf\" });\n * const url = URL.createObjectURL(blob);\n * const a = document.createElement(\"a\");\n * a.href = url;\n * a.download = \"annotated.pdf\";\n * a.click();\n * URL.revokeObjectURL(url);\n * ```\n *\n * @category Function\n */\nexport async function exportPdf(\n pdfSource: string | Uint8Array | ArrayBuffer,\n highlights: ExportableHighlight[],\n options: ExportPdfOptions = {}\n): Promise<Uint8Array> {\n // Load PDF\n let pdfBytes: ArrayBuffer;\n if (typeof pdfSource === \"string\") {\n const response = await fetch(pdfSource);\n pdfBytes = await response.arrayBuffer();\n } else {\n pdfBytes =\n pdfSource instanceof Uint8Array\n ? pdfSource.buffer.slice(\n pdfSource.byteOffset,\n pdfSource.byteOffset + pdfSource.byteLength\n )\n : pdfSource;\n }\n\n const pdfDoc = await PDFDocument.load(pdfBytes);\n const pages = pdfDoc.getPages();\n const font = await pdfDoc.embedFont(StandardFonts.Helvetica);\n\n // Group by page and render\n const byPage = groupByPage(highlights);\n const totalPages = byPage.size;\n let currentPage = 0;\n\n for (const [pageNum, pageHighlights] of byPage) {\n const page = pages[pageNum - 1]; // 1-indexed to 0-indexed\n if (!page) continue;\n\n for (const highlight of pageHighlights) {\n switch (highlight.type) {\n case \"text\":\n await renderTextHighlight(page, highlight, options);\n break;\n case \"area\":\n await renderAreaHighlight(page, highlight, options);\n break;\n case \"freetext\":\n await renderFreetextHighlight(page, highlight, options, font);\n break;\n case \"image\":\n await renderImageHighlight(pdfDoc, page, highlight);\n break;\n case \"drawing\":\n // Drawings are stored as PNG images, reuse image highlight rendering\n // but with contain-fit so an ink drawing keeps its aspect ratio.\n await renderImageHighlight(pdfDoc, page, highlight, \"drawing\");\n break;\n case \"shape\":\n await renderShapeHighlight(page, highlight);\n break;\n default:\n // Default to area highlight for backwards compatibility\n await renderAreaHighlight(page, highlight, options);\n }\n }\n\n currentPage++;\n options.onProgress?.(currentPage, totalPages);\n }\n\n return pdfDoc.save();\n}\n"]}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -459,6 +459,16 @@ type PdfHighlighterUtils = {
|
|
|
459
459
|
* Clear active search highlights.
|
|
460
460
|
*/
|
|
461
461
|
clearSearch(): void;
|
|
462
|
+
/**
|
|
463
|
+
* Marks a highlight as selected/deselected (its floating toolbar is open).
|
|
464
|
+
* Counter-backed, so overlapping select/deselect from different highlights
|
|
465
|
+
* resolves correctly. Used to suppress hover tips while a toolbar is shown.
|
|
466
|
+
*/
|
|
467
|
+
setHighlightSelected(selected: boolean): void;
|
|
468
|
+
/**
|
|
469
|
+
* Whether any highlight is currently selected (toolbar showing).
|
|
470
|
+
*/
|
|
471
|
+
isHighlightSelected(): boolean;
|
|
462
472
|
};
|
|
463
473
|
/**
|
|
464
474
|
* Custom hook for providing {@link PdfHighlighterUtils}. Must be used
|
|
@@ -816,13 +826,25 @@ interface TextHighlightProps {
|
|
|
816
826
|
* Default: ["rgba(255, 226, 143, 1)", "#ffcdd2", "#c8e6c9", "#bbdefb", "#e1bee7"]
|
|
817
827
|
*/
|
|
818
828
|
colorPresets?: string[];
|
|
829
|
+
/**
|
|
830
|
+
* Extra buttons rendered in the same toolbar row, after the copy button.
|
|
831
|
+
* Use this to add consumer-defined actions (e.g. a comment toggle) without
|
|
832
|
+
* needing a second, separate popup.
|
|
833
|
+
*/
|
|
834
|
+
extraButtons?: ReactNode;
|
|
835
|
+
/**
|
|
836
|
+
* Extra content rendered below the toolbar row, in the same slot as the
|
|
837
|
+
* built-in style panel (e.g. a comment editor opened by an extraButtons
|
|
838
|
+
* toggle).
|
|
839
|
+
*/
|
|
840
|
+
extraPanel?: ReactNode;
|
|
819
841
|
}
|
|
820
842
|
/**
|
|
821
843
|
* A component for displaying a highlighted text area.
|
|
822
844
|
*
|
|
823
845
|
* @category Component
|
|
824
846
|
*/
|
|
825
|
-
declare const TextHighlight: ({ highlight, onClick, onMouseOver, onMouseOut, isScrolledTo, onContextMenu, style, highlightColor, highlightStyle, onStyleChange, onDelete, styleIcon, deleteIcon, copyText, colorPresets, }: TextHighlightProps) => React.JSX.Element
|
|
847
|
+
declare const TextHighlight: React.MemoExoticComponent<({ highlight, onClick, onMouseOver, onMouseOut, isScrolledTo, onContextMenu, style, highlightColor, highlightStyle, onStyleChange, onDelete, styleIcon, deleteIcon, copyText, colorPresets, extraButtons, extraPanel, }: TextHighlightProps) => React.JSX.Element>;
|
|
826
848
|
|
|
827
849
|
/**
|
|
828
850
|
* The props type for {@link MonitoredHighlightContainer}.
|
|
@@ -923,10 +945,6 @@ interface AreaHighlightProps {
|
|
|
923
945
|
* Callback triggered when the delete button is clicked.
|
|
924
946
|
*/
|
|
925
947
|
onDelete?(): void;
|
|
926
|
-
/**
|
|
927
|
-
* Custom style icon. Replaces the default palette icon.
|
|
928
|
-
*/
|
|
929
|
-
styleIcon?: ReactNode;
|
|
930
948
|
/**
|
|
931
949
|
* Custom delete icon. Replaces the default trash icon.
|
|
932
950
|
*/
|
|
@@ -936,13 +954,25 @@ interface AreaHighlightProps {
|
|
|
936
954
|
* Default: ["rgba(255, 226, 143, 1)", "#ffcdd2", "#c8e6c9", "#bbdefb", "#e1bee7"]
|
|
937
955
|
*/
|
|
938
956
|
colorPresets?: string[];
|
|
957
|
+
/**
|
|
958
|
+
* Extra buttons rendered in the same toolbar row, after the copy button.
|
|
959
|
+
* Use this to add consumer-defined actions (e.g. a comment toggle) without
|
|
960
|
+
* needing a second, separate popup.
|
|
961
|
+
*/
|
|
962
|
+
extraButtons?: ReactNode;
|
|
963
|
+
/**
|
|
964
|
+
* Extra content rendered below the toolbar row, in the same slot as the
|
|
965
|
+
* built-in style panel (e.g. a comment editor opened by an extraButtons
|
|
966
|
+
* toggle).
|
|
967
|
+
*/
|
|
968
|
+
extraPanel?: ReactNode;
|
|
939
969
|
}
|
|
940
970
|
/**
|
|
941
971
|
* Renders a resizeable and interactive rectangular area for a highlight.
|
|
942
972
|
*
|
|
943
973
|
* @category Component
|
|
944
974
|
*/
|
|
945
|
-
declare const AreaHighlight: ({ highlight, onChange, isScrolledTo, bounds, onContextMenu, onEditStart, style, highlightColor, onStyleChange, onDelete,
|
|
975
|
+
declare const AreaHighlight: React.MemoExoticComponent<({ highlight, onChange, isScrolledTo, bounds, onContextMenu, onEditStart, style, highlightColor, onStyleChange, onDelete, deleteIcon, copyText, colorPresets, extraButtons, extraPanel, }: AreaHighlightProps) => React.JSX.Element>;
|
|
946
976
|
|
|
947
977
|
/**
|
|
948
978
|
* Style options for freetext highlight appearance.
|
|
@@ -1119,13 +1149,20 @@ interface ImageHighlightProps {
|
|
|
1119
1149
|
* Custom delete icon. Replaces the default trash icon.
|
|
1120
1150
|
*/
|
|
1121
1151
|
deleteIcon?: ReactNode;
|
|
1152
|
+
/**
|
|
1153
|
+
* Callback triggered when the image is rotated 90°. Receives the
|
|
1154
|
+
* re-rendered PNG data URL (rotated, dimensions swapped to match).
|
|
1155
|
+
* Pair with `onChange` to persist the swapped bounding rect this
|
|
1156
|
+
* component already computes and passes to it.
|
|
1157
|
+
*/
|
|
1158
|
+
onImageChange?(newImage: string): void;
|
|
1122
1159
|
}
|
|
1123
1160
|
/**
|
|
1124
1161
|
* Renders a draggable, resizable image/signature annotation.
|
|
1125
1162
|
*
|
|
1126
1163
|
* @category Component
|
|
1127
1164
|
*/
|
|
1128
|
-
declare const ImageHighlight: ({ highlight, onChange, isScrolledTo, bounds, onContextMenu, onEditStart, onEditEnd, style, dragIcon, onDelete, deleteIcon, }: ImageHighlightProps) => React.JSX.Element;
|
|
1165
|
+
declare const ImageHighlight: ({ highlight, onChange, isScrolledTo, bounds, onContextMenu, onEditStart, onEditEnd, style, dragIcon, onDelete, deleteIcon, onImageChange, }: ImageHighlightProps) => React.JSX.Element;
|
|
1129
1166
|
|
|
1130
1167
|
/**
|
|
1131
1168
|
* The props type for {@link SignaturePad}.
|
|
@@ -1578,7 +1615,7 @@ type PdfExtractedPage = {
|
|
|
1578
1615
|
textItems: PdfTextItem[];
|
|
1579
1616
|
columns?: PdfTextColumn[];
|
|
1580
1617
|
};
|
|
1581
|
-
type PdfTextUnitType = "paragraph" | "title" | "heading" | "author" | "affiliation" | "footnote" | "reference" | "unknown";
|
|
1618
|
+
type PdfTextUnitType = "paragraph" | "title" | "heading" | "author" | "affiliation" | "footnote" | "reference" | "caption" | "figureLabel" | "unknown";
|
|
1582
1619
|
type PdfTextUnit = {
|
|
1583
1620
|
id: string;
|
|
1584
1621
|
type: PdfTextUnitType;
|