react-pdf-highlighter-plus 1.1.4 → 1.3.0
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/README.md +152 -20
- 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 +149 -17
- package/dist/esm/index.js +2518 -1078
- 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 +75 -25
- 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 +30 -14
- package/dist/esm/export-pdf-W2QGWADM.js.map +0 -1
package/README.md
CHANGED
|
@@ -46,8 +46,13 @@
|
|
|
46
46
|
| **PDF Search** | Search through all PDF text with next/previous navigation |
|
|
47
47
|
| **PDF Export** | Export annotated PDF with all highlights embedded |
|
|
48
48
|
| **Local PDF Worker** | Uses the packaged PDF.js worker by default |
|
|
49
|
-
| **Light/Dark Theme** |
|
|
50
|
-
| **Zoom Support** |
|
|
49
|
+
| **Light/Dark Theme** | Hue-preserving dark mode (OKLab recolor) — photos & colors stay readable |
|
|
50
|
+
| **Zoom Support** | Buttons, **pinch / ctrl+wheel zoom**, position-independent data |
|
|
51
|
+
| **Smooth Scroll** | Animated scroll-to-highlight (respects reduced-motion) |
|
|
52
|
+
| **Deep Linking** | `initialPage` + `onPageChange` for `?page=N` style navigation |
|
|
53
|
+
| **Locate Text** | `getTextPosition` — turn any quote into a precise highlight (citations) |
|
|
54
|
+
| **Read Aloud** | Text-to-speech that highlights & follows each sentence (recipe) |
|
|
55
|
+
| **Fast Loading** | Progressive range loading, auth headers, document caching |
|
|
51
56
|
| **Fully Customizable** | Exposed styling on all components |
|
|
52
57
|
|
|
53
58
|
## Quick Links
|
|
@@ -299,10 +304,13 @@ highlighterUtilsRef.current?.clearSearch();
|
|
|
299
304
|
|
|
300
305
|
## Light/Dark Theme
|
|
301
306
|
|
|
302
|
-
|
|
307
|
+
Dark mode recolors each page **at render time** using a hue-preserving OKLab map —
|
|
308
|
+
white paper maps to a dark background and black text to a light foreground, while
|
|
309
|
+
**colors keep their hue and embedded photos keep their pixels** (unlike a CSS
|
|
310
|
+
`invert()` filter). Highlights, the text selection, and the left panel all adapt.
|
|
303
311
|
|
|
304
312
|
```tsx
|
|
305
|
-
// Enable dark mode
|
|
313
|
+
// Enable dark mode (warm-gray default palette)
|
|
306
314
|
<PdfHighlighter
|
|
307
315
|
pdfDocument={pdfDocument}
|
|
308
316
|
theme={{ mode: "dark" }}
|
|
@@ -311,12 +319,15 @@ Toggle between light and dark modes with customizable styling for comfortable re
|
|
|
311
319
|
<HighlightContainer />
|
|
312
320
|
</PdfHighlighter>
|
|
313
321
|
|
|
314
|
-
// Customize dark
|
|
322
|
+
// Customize the dark palette
|
|
315
323
|
<PdfHighlighter
|
|
316
324
|
pdfDocument={pdfDocument}
|
|
317
325
|
theme={{
|
|
318
326
|
mode: "dark",
|
|
319
|
-
|
|
327
|
+
darkModeColors: {
|
|
328
|
+
background: "#141210", // replaces white paper
|
|
329
|
+
foreground: "#eae6e0", // replaces black text / line-art
|
|
330
|
+
},
|
|
320
331
|
containerBackgroundColor: "#3a3a3a",
|
|
321
332
|
scrollbarThumbColor: "#6b6b6b",
|
|
322
333
|
scrollbarTrackColor: "#2c2c2c",
|
|
@@ -327,25 +338,146 @@ Toggle between light and dark modes with customizable styling for comfortable re
|
|
|
327
338
|
</PdfHighlighter>
|
|
328
339
|
```
|
|
329
340
|
|
|
330
|
-
**
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
334
|
-
-
|
|
335
|
-
-
|
|
336
|
-
|
|
337
|
-
**
|
|
338
|
-
|
|
339
|
-
|-------|--------|----------|
|
|
340
|
-
| `1.0` | Pure black | High contrast |
|
|
341
|
-
| `0.9` | Dark gray (~#1a1a1a) | **Recommended** |
|
|
342
|
-
| `0.85` | Softer gray (~#262626) | Long reading sessions |
|
|
343
|
-
| `0.8` | Medium gray (~#333333) | Maximum comfort |
|
|
341
|
+
**Highlights:**
|
|
342
|
+
- Hue-preserving recolor (red stays red, blue links stay blue); photos untouched.
|
|
343
|
+
- Highlights stay readable: translucent fill + a border, with no `mix-blend` wash-out.
|
|
344
|
+
- Scroll **and** zoom are preserved when toggling the theme.
|
|
345
|
+
- `LeftPanel` accepts `mode="dark"` so the outline/thumbnails panel matches.
|
|
346
|
+
- Drawing/shape default ink becomes white in dark mode.
|
|
347
|
+
|
|
348
|
+
> **Deprecated:** `theme.darkModeInvertIntensity` is ignored — dark mode no longer
|
|
349
|
+
> uses a CSS `invert()` filter. Use `theme.darkModeColors` instead.
|
|
344
350
|
|
|
345
351
|
[Full Documentation →](docs/theming.md)
|
|
346
352
|
|
|
347
353
|
---
|
|
348
354
|
|
|
355
|
+
## Loading & Performance
|
|
356
|
+
|
|
357
|
+
`PdfLoader` loads documents progressively and caches them.
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
<PdfLoader
|
|
361
|
+
document="https://api.example.com/files/report.pdf"
|
|
362
|
+
// Fetch only the pages needed to render first (needs server HTTP range support)
|
|
363
|
+
disableAutoFetch={true}
|
|
364
|
+
// Auth / cross-origin
|
|
365
|
+
httpHeaders={{ Authorization: `Bearer ${token}` }}
|
|
366
|
+
withCredentials={false}
|
|
367
|
+
// Reuse the same URL instantly on remount / re-open (default true)
|
|
368
|
+
enableCache={true}
|
|
369
|
+
>
|
|
370
|
+
{(pdfDocument) => <PdfHighlighter pdfDocument={pdfDocument} /* … */ />}
|
|
371
|
+
</PdfLoader>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Prop | Default | Description |
|
|
375
|
+
|------|---------|-------------|
|
|
376
|
+
| `disableAutoFetch` | `true` | Fetch pages on demand instead of the whole file (first page shows fast on range-capable servers) |
|
|
377
|
+
| `disableStream` | `false` | Disable progressive streaming |
|
|
378
|
+
| `rangeChunkSize` | pdf.js | Size of each range request in bytes |
|
|
379
|
+
| `httpHeaders` | – | Extra request headers (e.g. an auth token) |
|
|
380
|
+
| `withCredentials` | `false` | Send cookies with the request |
|
|
381
|
+
| `enableCache` | `true` | Cache the loaded document by URL (also dedupes StrictMode double-mount) |
|
|
382
|
+
| `beforeLoad` | spinner | Render while loading; receives progress or `null` |
|
|
383
|
+
|
|
384
|
+
> Progressive loading requires the server to support HTTP range requests
|
|
385
|
+
> (`Accept-Ranges: bytes`) **and** expose `Content-Range` via CORS.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Navigation, Zoom & Smooth Scroll
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
<PdfHighlighter
|
|
393
|
+
pdfDocument={pdfDocument}
|
|
394
|
+
initialPage={12} // jump here on first load (deep-link)
|
|
395
|
+
onPageChange={(page) => syncUrl(page)} // current page changed
|
|
396
|
+
onZoomChange={(scale) => setZoom(scale)} // pinch / ctrl+wheel zoom changed
|
|
397
|
+
highlights={highlights}
|
|
398
|
+
>
|
|
399
|
+
<HighlightContainer />
|
|
400
|
+
</PdfHighlighter>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
- **Pinch / ctrl(⌘)+wheel zoom** is built in — smooth (GPU transform during the
|
|
404
|
+
gesture, one crisp re-render on settle), anchored to the cursor.
|
|
405
|
+
- **`scrollToHighlight(highlight)`** (from `usePdfHighlighterContext`) now scrolls
|
|
406
|
+
**smoothly** and respects `prefers-reduced-motion`.
|
|
407
|
+
- `initialPage` is applied once on load; `onPageChange` fires as the visible page
|
|
408
|
+
changes — wire them to a `?page=N` URL for deep links.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Locating Text (Citations)
|
|
413
|
+
|
|
414
|
+
`getTextPosition` finds a piece of text in the PDF and returns a precise
|
|
415
|
+
`ScaledPosition` — turn an external quote (e.g. an AI citation) into a highlight
|
|
416
|
+
you can render or scroll to. Matching ignores whitespace/line-wraps and falls
|
|
417
|
+
back to fuzzy matching.
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
import { getTextPosition } from "react-pdf-highlighter-plus";
|
|
421
|
+
|
|
422
|
+
const match = await getTextPosition(pdfDocument, "the exact or near-exact quote");
|
|
423
|
+
if (match) {
|
|
424
|
+
const citation = {
|
|
425
|
+
id: "cite-1",
|
|
426
|
+
type: "text",
|
|
427
|
+
content: { text: match.matchedText },
|
|
428
|
+
position: match.position, // precise rects, page-independent
|
|
429
|
+
};
|
|
430
|
+
setHighlights((prev) => [citation, ...prev]);
|
|
431
|
+
utils.scrollToHighlight(citation); // smooth scroll + flash
|
|
432
|
+
}
|
|
433
|
+
// match: { position, pageNumber, matchedText, confidence: "exact" | "fuzzy" }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Read Aloud (Text-to-Speech)
|
|
439
|
+
|
|
440
|
+
Build a read-aloud / "PDF to audio" experience: speak the document and
|
|
441
|
+
highlight + auto-scroll to each sentence as it's read. The pieces are already
|
|
442
|
+
here — `extractSentences` gives the ordered script (text + position) and
|
|
443
|
+
`scrollToHighlight` follows along. The TTS engine is yours to choose (the
|
|
444
|
+
browser `speechSynthesis`, or a cloud voice).
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { extractSentences } from "react-pdf-highlighter-plus";
|
|
448
|
+
|
|
449
|
+
// 1. Build the ordered script once.
|
|
450
|
+
const sentences = (
|
|
451
|
+
await extractSentences(pdfDocument, { includePositions: true })
|
|
452
|
+
).filter((s) => s.position);
|
|
453
|
+
|
|
454
|
+
// 2. Speak each sentence; highlight + scroll as it starts.
|
|
455
|
+
function readFrom(index: number) {
|
|
456
|
+
const s = sentences[index];
|
|
457
|
+
if (!s) return;
|
|
458
|
+
|
|
459
|
+
const reading = {
|
|
460
|
+
id: "reading",
|
|
461
|
+
type: "text",
|
|
462
|
+
content: { text: s.text },
|
|
463
|
+
position: s.position!,
|
|
464
|
+
};
|
|
465
|
+
setHighlights((prev) => [reading, ...prev.filter((h) => h.id !== "reading")]);
|
|
466
|
+
utils.scrollToHighlight(reading); // smooth follow
|
|
467
|
+
|
|
468
|
+
const utter = new SpeechSynthesisUtterance(s.text);
|
|
469
|
+
utter.onend = () => readFrom(index + 1); // advance
|
|
470
|
+
speechSynthesis.speak(utter);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
readFrom(0);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Swap `speechSynthesis` for any engine — sentence-level sync needs no word
|
|
477
|
+
timing. See the example app for a full transport (play/pause/seek/speed/voice).
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
349
481
|
## PDF Export
|
|
350
482
|
|
|
351
483
|
Export your annotated PDF with all highlights embedded.
|
|
@@ -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":";;;AA4DA,SAAS,WAAW,KAAA,EAKlB;AAEA,EAAA,MAAM,YAAY,KAAA,CAAM,KAAA;AAAA,IACtB;AAAA,GACF;AACA,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,QAAA,CAAS,SAAA,CAAU,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,MAC5B,CAAA,EAAG,QAAA,CAAS,SAAA,CAAU,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,MAC5B,CAAA,EAAG,QAAA,CAAS,SAAA,CAAU,CAAC,CAAC,CAAA,GAAI,GAAA;AAAA,MAC5B,CAAA,EAAG,UAAU,CAAC,CAAA,GAAI,WAAW,SAAA,CAAU,CAAC,CAAC,CAAA,GAAI;AAAA,KAC/C;AAAA,EACF;AAGA,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,EAAE,CAAA;AACjC,EAAA,IAAI,GAAA,CAAI,WAAW,CAAA,EAAG;AACpB,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,SAAS,GAAA,CAAI,CAAC,IAAI,GAAA,CAAI,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG,SAAS,GAAA,CAAI,CAAC,IAAI,GAAA,CAAI,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG,SAAS,GAAA,CAAI,CAAC,IAAI,GAAA,CAAI,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG;AAAA,KACL;AAAA,EACF;AACA,EAAA,IAAI,GAAA,CAAI,WAAW,CAAA,EAAG;AACpB,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,SAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG,SAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG,SAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAC,CAAA,EAAG,EAAE,CAAA,GAAI,GAAA;AAAA,MACnC,CAAA,EAAG;AAAA,KACL;AAAA,EACF;AAGA,EAAA,OAAO,EAAE,GAAG,CAAA,EAAG,CAAA,EAAG,MAAM,CAAA,EAAG,IAAA,EAAM,GAAG,GAAA,EAAI;AAC1C;AAMA,SAAS,iBAAA,CACP,QACA,IAAA,EACyD;AACzD,EAAA,MAAM,QAAA,GAAW,KAAK,QAAA,EAAS;AAC/B,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,EAAU;AAGjC,EAAA,MAAM,MAAA,GAAS,WAAW,MAAA,CAAO,KAAA;AACjC,EAAA,MAAM,MAAA,GAAS,YAAY,MAAA,CAAO,MAAA;AAElC,EAAA,MAAM,CAAA,GAAI,OAAO,EAAA,GAAK,MAAA;AACtB,EAAA,MAAM,KAAA,GAAA,CAAS,MAAA,CAAO,EAAA,GAAK,MAAA,CAAO,EAAA,IAAM,MAAA;AACxC,EAAA,MAAM,MAAA,GAAA,CAAU,MAAA,CAAO,EAAA,GAAK,MAAA,CAAO,EAAA,IAAM,MAAA;AAGzC,EAAA,MAAM,CAAA,GAAI,SAAA,GAAY,MAAA,CAAO,EAAA,GAAK,MAAA,GAAS,MAAA;AAE3C,EAAA,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,MAAA,EAAO;AAC/B;AAKA,SAAS,eAAe,OAAA,EAGtB;AACA,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,KAAA,CAAM,GAAG,EAAE,CAAC,CAAA;AACnC,EAAA,MAAM,UAAA,GAAa,KAAK,MAAM,CAAA;AAC9B,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,UAAA,CAAW,MAAM,CAAA;AAC9C,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,UAAA,CAAW,QAAQ,CAAA,EAAA,EAAK;AAC1C,IAAA,KAAA,CAAM,CAAC,CAAA,GAAI,UAAA,CAAW,UAAA,CAAW,CAAC,CAAA;AAAA,EACpC;AACA,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,QAAA,CAAS,WAAW,IAAI,KAAA,GAAQ,KAAA;AACrD,EAAA,OAAO,EAAE,OAAO,IAAA,EAAK;AACvB;AAOA,SAAS,cAAc,OAAA,EAA0C;AAC/D,EAAA,MAAM,IAAA,GAAO,QAAQ,KAAA,CAAM,CAAA,EAAG,QAAQ,OAAA,CAAQ,GAAG,CAAC,CAAA,CAAE,WAAA,EAAY;AAChE,EAAA,IAAI,IAAA,KAAS,aAAa,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,KAAS,YAAA,IAAgB,IAAA,KAAS,WAAA,EAAa,OAAO,KAAA;AAC1D,EAAA,OAAO,OAAA;AACT;AAMA,IAAM,iBAAA,GAAoB,CAAA;AAG1B,SAAS,eAAA,CACP,GAAA,EACA,CAAA,EACA,CAAA,EACA,CAAA,EACM;AACN,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAC,CAAC,CAAA;AACpD,EAAA,GAAA,CAAI,SAAA,EAAU;AACd,EAAA,GAAA,CAAI,MAAA,CAAO,QAAQ,CAAC,CAAA;AACpB,EAAA,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,MAAM,CAAA;AAC5B,EAAA,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,MAAM,CAAA;AAC5B,EAAA,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,MAAM,CAAA;AAC5B,EAAA,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,MAAM,CAAA;AAC5B,EAAA,GAAA,CAAI,SAAA,EAAU;AAChB;AAqBA,eAAe,4BAAA,CACb,OAAA,EACA,WAAA,EACA,YAAA,EACA,IAAA,EAC4B;AAC5B,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,OAAO,UAAU,WAAA,EAAa;AACnE,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,GAAA,GAAM,MAAM,IAAI,OAAA,CAAiC,CAAC,OAAA,KAAY;AAClE,IAAA,MAAM,KAAA,GAAQ,IAAI,KAAA,EAAM;AACxB,IAAA,KAAA,CAAM,MAAA,GAAS,MAAM,OAAA,CAAQ,KAAK,CAAA;AAClC,IAAA,KAAA,CAAM,OAAA,GAAU,MAAM,OAAA,CAAQ,IAAI,CAAA;AAClC,IAAA,KAAA,CAAM,GAAA,GAAM,OAAA;AAAA,EACd,CAAC,CAAA;AACD,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAIjB,EAAA,MAAM,KAAA,GAAQ,CAAA;AACd,EAAA,MAAM,EAAA,GAAK,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,WAAA,GAAc,KAAK,CAAC,CAAA;AACtD,EAAA,MAAM,EAAA,GAAK,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,YAAA,GAAe,KAAK,CAAC,CAAA;AAEvD,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,EAAA,MAAA,CAAO,KAAA,GAAQ,EAAA;AACf,EAAA,MAAA,CAAO,MAAA,GAAS,EAAA;AAChB,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAClC,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AAIjB,EAAA,eAAA,CAAgB,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,iBAAA,GAAoB,KAAK,CAAA;AACtD,EAAA,GAAA,CAAI,IAAA,EAAK;AAET,EAAA,IAAI,KAAK,UAAA,EAAY;AACnB,IAAA,GAAA,CAAI,YAAY,IAAA,CAAK,UAAA;AACrB,IAAA,GAAA,CAAI,QAAA,CAAS,CAAA,EAAG,CAAA,EAAG,EAAA,EAAI,EAAE,CAAA;AAAA,EAC3B;AAEA,EAAA,MAAM,EAAA,GAAK,IAAI,YAAA,IAAgB,EAAA;AAC/B,EAAA,MAAM,EAAA,GAAK,IAAI,aAAA,IAAiB,EAAA;AAEhC,EAAA,IAAI,IAAA,CAAK,QAAQ,SAAA,EAAW;AAC1B,IAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,EAAA,GAAK,EAAA,EAAI,KAAK,EAAE,CAAA;AACnC,IAAA,MAAM,KAAK,EAAA,GAAK,CAAA;AAChB,IAAA,MAAM,KAAK,EAAA,GAAK,CAAA;AAChB,IAAA,GAAA,CAAI,SAAA,CAAU,MAAM,EAAA,GAAK,EAAA,IAAM,IAAI,EAAA,GAAK,EAAA,IAAM,CAAA,EAAG,EAAA,EAAI,EAAE,CAAA;AAAA,EACzD,CAAA,MAAO;AACL,IAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,CAAA,EAAG,CAAA,EAAG,IAAI,EAAE,CAAA;AAAA,EACjC;AAEA,EAAA,OAAO,cAAA,CAAe,MAAA,CAAO,SAAA,CAAU,WAAW,CAAC,CAAA,CAAE,KAAA;AACvD;AAUA,SAAS,oBAAA,CACP,MACA,CAAA,EASM;AACN,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,CAAE,KAAA,GAAQ,CAAA,EAAG,CAAA,CAAE,MAAA,GAAS,CAAC,CAAC,CAAA;AACnE,EAAA,IAAI,KAAK,CAAA,EAAG;AACV,IAAA,IAAA,CAAK,aAAA,CAAc;AAAA,MACjB,GAAG,CAAA,CAAE,CAAA;AAAA,MACL,GAAG,CAAA,CAAE,CAAA;AAAA,MACL,OAAO,CAAA,CAAE,KAAA;AAAA,MACT,QAAQ,CAAA,CAAE,MAAA;AAAA,MACV,OAAO,CAAA,CAAE,KAAA;AAAA,MACT,SAAS,CAAA,CAAE;AAAA,KACZ,CAAA;AACD,IAAA;AAAA,EACF;AACA,EAAA,MAAM,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,GAAE,GAAI,CAAA;AAGhC,EAAA,MAAM,OACJ,CAAA,EAAA,EAAK,CAAC,QAAQ,CAAA,GAAI,CAAC,MAAM,CAAC,CAAA,CAAA,EAAI,CAAC,CAAA,OAAA,EAAU,CAAC,IAAI,CAAC,CAAA,GAAA,EAC1C,IAAI,CAAC,CAAA,GAAA,EAAM,CAAC,CAAA,CAAA,EAAI,CAAC,CAAA,OAAA,EAAU,CAAA,GAAI,CAAC,CAAA,CAAA,EAAI,CAAC,MACrC,CAAC,CAAA,GAAA,EAAM,CAAC,CAAA,CAAA,EAAI,CAAC,YAAY,CAAA,GAAI,CAAC,MAC9B,CAAC,CAAA,GAAA,EAAM,CAAC,CAAA,CAAA,EAAI,CAAC,UAAU,CAAC,CAAA,IAAA,CAAA;AAC/B,EAAA,IAAA,CAAK,YAAY,IAAA,EAAM;AAAA,IACrB,GAAG,CAAA,CAAE,CAAA;AAAA,IACL,CAAA,EAAG,EAAE,CAAA,GAAI,CAAA;AAAA,IACT,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,SAAS,CAAA,CAAE;AAAA,GACZ,CAAA;AACH;AAMA,SAAS,QAAA,CACP,IAAA,EACA,IAAA,EACA,QAAA,EACA,QAAA,EACU;AACV,EAAA,IAAI,CAAC,IAAA,IAAQ,QAAA,IAAY,CAAA,SAAU,EAAC;AAEpC,EAAA,MAAM,QAAkB,EAAC;AAGzB,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAElC,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,IAAI,CAAC,SAAA,CAAU,IAAA,EAAK,EAAG;AACrB,MAAA,KAAA,CAAM,KAAK,EAAE,CAAA;AACb,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,KAAK,CAAA;AACnC,IAAA,IAAI,WAAA,GAAc,EAAA;AAElB,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,WAAW,WAAA,GAAc,CAAA,EAAG,WAAW,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,GAAK,IAAA;AAC1D,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,iBAAA,CAAkB,QAAA,EAAU,QAAQ,CAAA;AAE3D,MAAA,IAAI,aAAa,QAAA,EAAU;AACzB,QAAA,WAAA,GAAc,QAAA;AAAA,MAChB,CAAA,MAAO;AAEL,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,KAAA,CAAM,KAAK,WAAW,CAAA;AACtB,UAAA,WAAA,GAAc,EAAA;AAAA,QAChB;AAGA,QAAA,IAAI,IAAA,CAAK,iBAAA,CAAkB,IAAA,EAAM,QAAQ,IAAI,QAAA,EAAU;AACrD,UAAA,IAAI,SAAA,GAAY,IAAA;AAChB,UAAA,OAAO,SAAA,CAAU,SAAS,CAAA,EAAG;AAC3B,YAAA,IAAI,SAAA,GAAY,CAAA;AAEhB,YAAA,OACE,SAAA,GAAY,SAAA,CAAU,MAAA,IACtB,IAAA,CAAK,iBAAA,CAAkB,SAAA,CAAU,SAAA,CAAU,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA,EAAG,QAAQ,KAAK,QAAA,EAC3E;AACA,cAAA,SAAA,EAAA;AAAA,YACF;AACA,YAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,SAAA,CAAU,CAAA,EAAG,SAAS,CAAA;AAC9C,YAAA,SAAA,GAAY,SAAA,CAAU,UAAU,SAAS,CAAA;AAEzC,YAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AAExB,cAAA,KAAA,CAAM,KAAK,KAAK,CAAA;AAAA,YAClB,CAAA,MAAO;AAEL,cAAA,WAAA,GAAc,KAAA;AAAA,YAChB;AAAA,UACF;AAAA,QACF,CAAA,MAAO;AACL,UAAA,WAAA,GAAc,IAAA;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AACA,IAAA,IAAI,WAAA,EAAa,KAAA,CAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EACzC;AAEA,EAAA,OAAO,KAAA;AACT;AAKA,SAAS,YACP,UAAA,EACoC;AACpC,EAAA,MAAM,GAAA,uBAAU,GAAA,EAAmC;AACnD,EAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC1B,IAAA,MAAM,OAAA,GAAU,CAAA,CAAE,QAAA,CAAS,YAAA,CAAa,UAAA;AACxC,IAAA,IAAI,CAAC,IAAI,GAAA,CAAI,OAAO,GAAG,GAAA,CAAI,GAAA,CAAI,OAAA,EAAS,EAAE,CAAA;AAC1C,IAAA,GAAA,CAAI,GAAA,CAAI,OAAO,CAAA,CAAG,IAAA,CAAK,CAAC,CAAA;AAAA,EAC1B;AACA,EAAA,OAAO,GAAA;AACT;AAMA,eAAe,mBAAA,CACb,IAAA,EACA,SAAA,EACA,OAAA,EACe;AAEf,EAAA,MAAM,QAAA,GACJ,SAAA,CAAU,cAAA,IACV,OAAA,CAAQ,kBAAA,IACR,0BAAA;AACF,EAAA,MAAM,KAAA,GAAQ,WAAW,QAAQ,CAAA;AACjC,EAAA,MAAM,cAAA,GAAiB,UAAU,cAAA,IAAkB,WAAA;AAGnD,EAAA,MAAM,KAAA,GACJ,SAAA,CAAU,QAAA,CAAS,KAAA,CAAM,MAAA,GAAS,CAAA,GAC9B,SAAA,CAAU,QAAA,CAAS,KAAA,GACnB,CAAC,SAAA,CAAU,QAAA,CAAS,YAAY,CAAA;AAEtC,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,EAAE,GAAG,CAAA,EAAG,KAAA,EAAO,QAAO,GAAI,iBAAA,CAAkB,MAAM,IAAI,CAAA;AAE5D,IAAA,IAAI,mBAAmB,WAAA,EAAa;AAIlC,MAAA,oBAAA,CAAqB,IAAA,EAAM;AAAA,QACzB,CAAA;AAAA,QACA,CAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAS,KAAA,CAAM,CAAA;AAAA,QACf,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,mBAAmB,WAAA,EAAa;AAEzC,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAS,GAAG,CAAA;AAC9C,MAAA,IAAA,CAAK,aAAA,CAAc;AAAA,QACjB,CAAA;AAAA,QACA,CAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,mBAAmB,eAAA,EAAiB;AAE7C,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,SAAS,GAAG,CAAA;AAC9C,MAAA,MAAM,KAAA,GAAQ,CAAA,GAAI,MAAA,GAAS,CAAA,GAAI,aAAA,GAAgB,CAAA;AAC/C,MAAA,IAAA,CAAK,aAAA,CAAc;AAAA,QACjB,CAAA;AAAA,QACA,CAAA,EAAG,KAAA;AAAA,QACH,KAAA;AAAA,QACA,MAAA,EAAQ,aAAA;AAAA,QACR,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AAAA,IACH;AAAA,EACF;AACF;AAKA,eAAe,mBAAA,CACb,IAAA,EACA,SAAA,EACA,OAAA,EACe;AAEf,EAAA,MAAM,QAAA,GACJ,SAAA,CAAU,cAAA,IACV,OAAA,CAAQ,kBAAA,IACR,0BAAA;AACF,EAAA,MAAM,KAAA,GAAQ,WAAW,QAAQ,CAAA;AACjC,EAAA,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,QAAO,GAAI,iBAAA;AAAA,IAC9B,UAAU,QAAA,CAAS,YAAA;AAAA,IACnB;AAAA,GACF;AAEA,EAAA,oBAAA,CAAqB,IAAA,EAAM;AAAA,IACzB,CAAA;AAAA,IACA,CAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,IACpC,SAAS,KAAA,CAAM,CAAA;AAAA,IACf,MAAA,EAAQ;AAAA,GACT,CAAA;AACH;AAMA,eAAe,uBAAA,CACb,IAAA,EACA,SAAA,EACA,OAAA,EACA,IAAA,EACe;AACf,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,OAAA,EAAS,IAAA,IAAQ,EAAA;AACxC,EAAA,MAAM,SAAA,GAAY,UAAA;AAAA,IAChB,SAAA,CAAU,KAAA,IAAS,OAAA,CAAQ,oBAAA,IAAwB;AAAA,GACrD;AAGA,EAAA,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,QAAO,GAAI,iBAAA;AAAA,IAC9B,UAAU,QAAA,CAAS,YAAA;AAAA,IACnB;AAAA,GACF;AAIA,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,EAAU;AACjC,EAAA,MAAM,MAAA,GAAS,SAAA,GAAY,SAAA,CAAU,QAAA,CAAS,YAAA,CAAa,MAAA;AAC3D,EAAA,MAAM,iBACJ,QAAA,CAAS,SAAA,CAAU,YAAY,EAAE,CAAA,IAAK,QAAQ,uBAAA,IAA2B,EAAA;AAC3E,EAAA,MAAM,WAAW,cAAA,GAAiB,MAAA;AAElC,EAAA,OAAA,CAAQ,IAAI,kBAAA,EAAoB;AAAA,IAC9B,cAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA,EAAe,EAAE,CAAA,EAAG,CAAA,EAAG,OAAO,MAAA,EAAO;AAAA,IACrC,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,CAAA,EAAG,EAAE;AAAA,GAC3B,CAAA;AAGD,EAAA,MAAM,YAAA,GAAe,SAAA,CAAU,eAAA,IAAmB,OAAA,CAAQ,sBAAA,IAA0B,SAAA;AACpF,EAAA,IAAI,iBAAiB,aAAA,EAAe;AAClC,IAAA,MAAM,OAAA,GAAU,WAAW,YAAY,CAAA;AACvC,IAAA,oBAAA,CAAqB,IAAA,EAAM;AAAA,MACzB,CAAA;AAAA,MACA,CAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAO,GAAA,CAAI,OAAA,CAAQ,GAAG,OAAA,CAAQ,CAAA,EAAG,QAAQ,CAAC,CAAA;AAAA,MAC1C,SAAS,OAAA,CAAQ,CAAA;AAAA,MACjB,MAAA,EAAQ;AAAA,KACT,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,UAAU,CAAA,GAAI,MAAA;AACpB,EAAA,MAAM,QAAA,GAAW,QAAQ,OAAA,GAAU,CAAA;AACnC,EAAA,MAAM,aAAa,QAAA,GAAW,GAAA;AAE9B,EAAA,IAAI,QAAA,GAAW,KAAK,IAAA,EAAM;AACxB,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,IAAA,EAAM,IAAA,EAAM,UAAU,QAAQ,CAAA;AACrD,IAAA,IAAI,QAAA,GAAW,CAAA,GAAI,MAAA,GAAS,QAAA,GAAW,OAAA;AAEvC,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AAExB,MAAA,IAAI,QAAA,GAAW,IAAI,OAAA,EAAS;AAG5B,MAAA,IAAI,IAAA,CAAK,MAAK,EAAG;AACf,QAAA,IAAA,CAAK,SAAS,IAAA,EAAM;AAAA,UAClB,GAAG,CAAA,GAAI,OAAA;AAAA,UACP,CAAA,EAAG,QAAA;AAAA,UACH,IAAA,EAAM,QAAA;AAAA,UACN,IAAA;AAAA,UACA,OAAO,GAAA,CAAI,SAAA,CAAU,GAAG,SAAA,CAAU,CAAA,EAAG,UAAU,CAAC;AAAA,SACjD,CAAA;AAAA,MACH;AAEA,MAAA,QAAA,IAAY,UAAA;AAAA,IACd;AAAA,EACF;AACF;AAMA,SAAS,yBAAA,CACP,IAAA,EACA,CAAA,EACA,CAAA,EACA,OACA,MAAA,EACyD;AACzD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,WAAA,EAAY,CAAE,KAAA;AACpC,EAAA,MAAM,SAAA,GAAY,KAAK,QAAA,EAAS;AAChC,EAAA,MAAM,UAAA,GAAa,KAAK,SAAA,EAAU;AAElC,EAAA,IAAI,aAAa,EAAA,EAAI;AAGnB,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,CAAA;AAAA,MACH,CAAA,EAAG,YAAY,CAAA,GAAI,KAAA;AAAA,MACnB,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ;AAAA,KACV;AAAA,EACF,CAAA,MAAA,IAAW,aAAa,GAAA,EAAK;AAE3B,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,YAAY,CAAA,GAAI,KAAA;AAAA,MACnB,CAAA,EAAG,aAAa,CAAA,GAAI,MAAA;AAAA,MACpB,KAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,MAAA,IAAW,aAAa,GAAA,EAAK;AAE3B,IAAA,OAAO;AAAA,MACL,CAAA,EAAG,aAAa,CAAA,GAAI,MAAA;AAAA,MACpB,CAAA,EAAG,CAAA;AAAA,MACH,KAAA,EAAO,MAAA;AAAA,MACP,MAAA,EAAQ;AAAA,KACV;AAAA,EACF;AAGA,EAAA,OAAO,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,MAAA,EAAO;AAC/B;AAYA,eAAe,oBAAA,CACb,MAAA,EACA,IAAA,EACA,SAAA,EACA,OAA4B,OAAA,EACb;AACf,EAAA,MAAM,YAAA,GAAe,UAAU,OAAA,EAAS,KAAA;AACxC,EAAA,IAAI,CAAC,YAAA,EAAc;AAEnB,EAAA,IAAI;AAEF,IAAA,MAAM,YAAA,GAAe,iBAAA;AAAA,MACnB,UAAU,QAAA,CAAS,YAAA;AAAA,MACnB;AAAA,KACF;AAUA,IAAA,IAAI,KAAA;AACJ,IAAA,MAAM,WAAW,MAAM,4BAAA;AAAA,MACrB,YAAA;AAAA,MACA,YAAA,CAAa,KAAA;AAAA,MACb,YAAA,CAAa,MAAA;AAAA,MACb,EAAE,GAAA,EAAK,IAAA,KAAS,SAAA,GAAY,YAAY,MAAA;AAAO,KACjD;AACA,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA;AAAA,IACxC,CAAA,MAAO;AACL,MAAA,MAAM,MAAA,GAAS,cAAc,YAAY,CAAA;AACzC,MAAA,IAAI,WAAW,KAAA,EAAO;AACpB,QAAA,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAA,CAAS,cAAA,CAAe,YAAY,EAAE,KAAK,CAAA;AAAA,MAClE,CAAA,MAAA,IAAW,WAAW,KAAA,EAAO;AAC3B,QAAA,KAAA,GAAQ,MAAM,MAAA,CAAO,QAAA,CAAS,cAAA,CAAe,YAAY,EAAE,KAAK,CAAA;AAAA,MAClE,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAM,mEAAmE,CAAA;AACjF,QAAA;AAAA,MACF;AAAA,IACF;AAGA,IAAA,MAAM,SAAA,GAAY,yBAAA;AAAA,MAChB,IAAA;AAAA,MACA,YAAA,CAAa,CAAA;AAAA,MACb,YAAA,CAAa,CAAA;AAAA,MACb,YAAA,CAAa,KAAA;AAAA,MACb,YAAA,CAAa;AAAA,KACf;AAEA,IAAA,OAAA,CAAQ,IAAI,eAAA,EAAiB;AAAA,MAC3B,QAAA,EAAU,IAAA,CAAK,WAAA,EAAY,CAAE,KAAA;AAAA,MAC7B,YAAA;AAAA,MACA;AAAA,KACD,CAAA;AAGD,IAAA,IAAA,CAAK,UAAU,KAAA,EAAO;AAAA,MACpB,GAAG,SAAA,CAAU,CAAA;AAAA,MACb,GAAG,SAAA,CAAU,CAAA;AAAA,MACb,OAAO,SAAA,CAAU,KAAA;AAAA,MACjB,QAAQ,SAAA,CAAU;AAAA,KACnB,CAAA;AAAA,EACH,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,KAAA,CAAM,0BAA0B,KAAK,CAAA;AAAA,EAC/C;AACF;AAKA,eAAe,oBAAA,CACb,MACA,SAAA,EACe;AAEf,EAAA,MAAM,YAAY,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,SAAA,IAAa,UAAU,SAAA,IAAa,WAAA;AAChF,EAAA,MAAM,iBAAiB,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,WAAA,IAAe,UAAU,WAAA,IAAe,SAAA;AACzF,EAAA,MAAM,cAAc,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,WAAA,IAAe,UAAU,WAAA,IAAe,CAAA;AAEtF,EAAA,MAAM,KAAA,GAAQ,WAAW,cAAc,CAAA;AACvC,EAAA,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,QAAO,GAAI,iBAAA;AAAA,IAC9B,UAAU,QAAA,CAAS,YAAA;AAAA,IACnB;AAAA,GACF;AAEA,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,WAAA;AACH,MAAA,IAAA,CAAK,aAAA,CAAc;AAAA,QACjB,CAAA;AAAA,QACA,CAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA;AAAA,QACA,aAAa,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QAC1C,WAAA,EAAa,WAAA;AAAA,QACb,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AACD,MAAA;AAAA,IAEF,KAAK,QAAA;AACH,MAAA,IAAA,CAAK,WAAA,CAAY;AAAA,QACf,CAAA,EAAG,IAAI,KAAA,GAAQ,CAAA;AAAA,QACf,CAAA,EAAG,IAAI,MAAA,GAAS,CAAA;AAAA,QAChB,QAAQ,KAAA,GAAQ,CAAA;AAAA,QAChB,QAAQ,MAAA,GAAS,CAAA;AAAA,QACjB,aAAa,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QAC1C,WAAA,EAAa,WAAA;AAAA,QACb,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AACD,MAAA;AAAA,IAEF,KAAK,OAAA,EAAS;AAEZ,MAAA,MAAM,OAAA,GAAU,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,UAAA;AAC1C,MAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,OAAA,EAAS,KAAA,EAAO,QAAA;AAIxC,MAAA,MAAM,MAAA,GAAS,OAAA,GAAU,CAAA,GAAI,OAAA,CAAQ,IAAI,KAAA,GAAQ,CAAA;AACjD,MAAA,MAAM,MAAA,GAAS,UAAU,CAAA,GAAA,CAAK,CAAA,GAAI,QAAQ,CAAA,IAAK,MAAA,GAAS,IAAI,MAAA,GAAS,CAAA;AACrE,MAAA,MAAM,OAAO,KAAA,GAAQ,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,QAAQ,CAAA,GAAI,KAAA;AAC/C,MAAA,MAAM,IAAA,GAAO,QAAQ,CAAA,GAAA,CAAK,CAAA,GAAI,MAAM,CAAA,IAAK,MAAA,GAAS,IAAI,MAAA,GAAS,CAAA;AAG/D,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,KAAA,EAAO,EAAE,CAAA,EAAG,MAAA,EAAQ,GAAG,MAAA,EAAO;AAAA,QAC9B,GAAA,EAAK,EAAE,CAAA,EAAG,IAAA,EAAM,GAAG,IAAA,EAAK;AAAA,QACxB,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAA,EAAW,WAAA;AAAA,QACX,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AAGD,MAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,MAAA,EAAQ,OAAO,MAAM,CAAA;AACrD,MAAA,MAAM,YAAY,IAAA,CAAK,GAAA,CAAI,IAAI,KAAA,GAAQ,GAAA,EAAK,SAAS,GAAG,CAAA;AACxD,MAAA,MAAM,UAAA,GAAa,KAAK,EAAA,GAAK,CAAA;AAG7B,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,KAAA,EAAO;AAAA,UACL,GAAG,IAAA,GAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,QAAQ,UAAU,CAAA;AAAA,UACjD,GAAG,IAAA,GAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,QAAQ,UAAU;AAAA,SACnD;AAAA,QACA,GAAA,EAAK,EAAE,CAAA,EAAG,IAAA,EAAM,GAAG,IAAA,EAAK;AAAA,QACxB,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAA,EAAW,WAAA;AAAA,QACX,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AACD,MAAA,IAAA,CAAK,QAAA,CAAS;AAAA,QACZ,KAAA,EAAO;AAAA,UACL,GAAG,IAAA,GAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,QAAQ,UAAU,CAAA;AAAA,UACjD,GAAG,IAAA,GAAO,SAAA,GAAY,IAAA,CAAK,GAAA,CAAI,QAAQ,UAAU;AAAA,SACnD;AAAA,QACA,GAAA,EAAK,EAAE,CAAA,EAAG,IAAA,EAAM,GAAG,IAAA,EAAK;AAAA,QACxB,OAAO,GAAA,CAAI,KAAA,CAAM,GAAG,KAAA,CAAM,CAAA,EAAG,MAAM,CAAC,CAAA;AAAA,QACpC,SAAA,EAAW,WAAA;AAAA,QACX,SAAS,KAAA,CAAM;AAAA,OAChB,CAAA;AACD,MAAA;AAAA,IACF;AAAA;AAEJ;AA6BA,eAAsB,SAAA,CACpB,SAAA,EACA,UAAA,EACA,OAAA,GAA4B,EAAC,EACR;AAErB,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,OAAO,cAAc,QAAA,EAAU;AACjC,IAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,SAAS,CAAA;AACtC,IAAA,QAAA,GAAW,MAAM,SAAS,WAAA,EAAY;AAAA,EACxC,CAAA,MAAO;AACL,IAAA,QAAA,GACE,SAAA,YAAqB,UAAA,GACjB,SAAA,CAAU,MAAA,CAAO,KAAA;AAAA,MACf,SAAA,CAAU,UAAA;AAAA,MACV,SAAA,CAAU,aAAa,SAAA,CAAU;AAAA,KACnC,GACA,SAAA;AAAA,EACR;AAEA,EAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,IAAA,CAAK,QAAQ,CAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,OAAO,QAAA,EAAS;AAC9B,EAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,SAAA,CAAU,cAAc,SAAS,CAAA;AAG3D,EAAA,MAAM,MAAA,GAAS,YAAY,UAAU,CAAA;AACrC,EAAA,MAAM,aAAa,MAAA,CAAO,IAAA;AAC1B,EAAA,IAAI,WAAA,GAAc,CAAA;AAElB,EAAA,KAAA,MAAW,CAAC,OAAA,EAAS,cAAc,CAAA,IAAK,MAAA,EAAQ;AAC9C,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,OAAA,GAAU,CAAC,CAAA;AAC9B,IAAA,IAAI,CAAC,IAAA,EAAM;AAEX,IAAA,KAAA,MAAW,aAAa,cAAA,EAAgB;AACtC,MAAA,QAAQ,UAAU,IAAA;AAAM,QACtB,KAAK,MAAA;AACH,UAAA,MAAM,mBAAA,CAAoB,IAAA,EAAM,SAAA,EAAW,OAAO,CAAA;AAClD,UAAA;AAAA,QACF,KAAK,MAAA;AACH,UAAA,MAAM,mBAAA,CAAoB,IAAA,EAAM,SAAA,EAAW,OAAO,CAAA;AAClD,UAAA;AAAA,QACF,KAAK,UAAA;AACH,UAAA,MAAM,uBAAA,CAAwB,IAAA,EAAM,SAAA,EAAW,OAAA,EAAS,IAAI,CAAA;AAC5D,UAAA;AAAA,QACF,KAAK,OAAA;AACH,UAAA,MAAM,oBAAA,CAAqB,MAAA,EAAQ,IAAA,EAAM,SAAS,CAAA;AAClD,UAAA;AAAA,QACF,KAAK,SAAA;AAGH,UAAA,MAAM,oBAAA,CAAqB,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAW,SAAS,CAAA;AAC7D,UAAA;AAAA,QACF,KAAK,OAAA;AACH,UAAA,MAAM,oBAAA,CAAqB,MAAM,SAAS,CAAA;AAC1C,UAAA;AAAA,QACF;AAEE,UAAA,MAAM,mBAAA,CAAoB,IAAA,EAAM,SAAA,EAAW,OAAO,CAAA;AAAA;AACtD,IACF;AAEA,IAAA,WAAA,EAAA;AACA,IAAA,OAAA,CAAQ,UAAA,GAAa,aAAa,UAAU,CAAA;AAAA,EAC9C;AAEA,EAAA,OAAO,OAAO,IAAA,EAAK;AACrB","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"]}
|