@embedpdf/engines 1.0.7 → 1.0.9

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/pdfium.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NoopLogger, PdfTaskHelper, PdfErrorCode, Task, Rotation, PdfAnnotationSubtype, stripPdfUnwantedMarkers, dateToPdfDate, PdfPageObjectType, pdfAlphaColorToWebAlphaColor, webAlphaColorToPdfAlphaColor, AppearanceMode, quadToRect, pdfDateToDate, PDF_FORM_FIELD_TYPE, toIntRect, transformRect, toIntSize, transformSize, PdfActionType, PdfZoomMode, MatchFlag, rectToQuad } from '@embedpdf/models';
1
+ import { NoopLogger, PdfTaskHelper, PdfErrorCode, Task, Rotation, PdfAnnotationSubtype, stripPdfUnwantedMarkers, PdfAnnotationBorderStyle, dateToPdfDate, PdfAnnotationColorType, PdfPageObjectType, pdfAlphaColorToWebAlphaColor, webAlphaColorToPdfAlphaColor, quadToRect, pdfDateToDate, flagsToNames, PDF_FORM_FIELD_TYPE, toIntRect, transformRect, makeMatrix, AppearanceMode, toIntSize, transformSize, PdfActionType, PdfZoomMode, MatchFlag, rectToQuad } from '@embedpdf/models';
2
2
  import { init } from '@embedpdf/pdfium';
3
3
 
4
4
  /**
@@ -910,7 +910,7 @@ class PdfiumEngine {
910
910
  let isSucceed = false;
911
911
  switch (annotation.type) {
912
912
  case PdfAnnotationSubtype.INK:
913
- isSucceed = this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, annotation.inkList);
913
+ isSucceed = this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, annotation);
914
914
  break;
915
915
  case PdfAnnotationSubtype.STAMP:
916
916
  isSucceed = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotationPtr, annotation.rect, annotation.contents);
@@ -919,7 +919,7 @@ class PdfiumEngine {
919
919
  case PdfAnnotationSubtype.STRIKEOUT:
920
920
  case PdfAnnotationSubtype.SQUIGGLY:
921
921
  case PdfAnnotationSubtype.HIGHLIGHT:
922
- isSucceed = this.addTextMarkupContent(page, annotationPtr, annotation);
922
+ isSucceed = this.addTextMarkupContent(page, pageCtx.pagePtr, annotationPtr, annotation);
923
923
  break;
924
924
  }
925
925
  if (!isSucceed) {
@@ -931,6 +931,7 @@ class PdfiumEngine {
931
931
  message: 'can not add content of the annotation',
932
932
  });
933
933
  }
934
+ this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotationPtr);
934
935
  this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
935
936
  const annotId = this.pdfiumModule.FPDFPage_GetAnnotIndex(pageCtx.pagePtr, annotationPtr);
936
937
  this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
@@ -988,7 +989,7 @@ class PdfiumEngine {
988
989
  /* clear every existing stroke first */
989
990
  if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotPtr))
990
991
  break;
991
- ok = this.addInkStroke(page, pageCtx.pagePtr, annotPtr, annotation.inkList);
992
+ ok = this.addInkStroke(page, pageCtx.pagePtr, annotPtr, annotation);
992
993
  break;
993
994
  }
994
995
  /* ── Stamp ───────────────────────────────────────────────────────────── */
@@ -1006,7 +1007,7 @@ class PdfiumEngine {
1006
1007
  case PdfAnnotationSubtype.STRIKEOUT:
1007
1008
  case PdfAnnotationSubtype.SQUIGGLY: {
1008
1009
  /* replace quad-points / colour / strings in one go */
1009
- ok = this.addTextMarkupContent(page, annotPtr, annotation, true);
1010
+ ok = this.addTextMarkupContent(page, pageCtx.pagePtr, annotPtr, annotation);
1010
1011
  break;
1011
1012
  }
1012
1013
  /* ── Unsupported edits – fall through to error ───────────────────────── */
@@ -1015,6 +1016,7 @@ class PdfiumEngine {
1015
1016
  }
1016
1017
  /* 3 ── regenerate appearance if payload was changed ───────────────────── */
1017
1018
  if (ok) {
1019
+ this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotPtr);
1018
1020
  this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1019
1021
  }
1020
1022
  /* 4 ── tidy-up native handles ──────────────────────────────────────────── */
@@ -1028,85 +1030,6 @@ class PdfiumEngine {
1028
1030
  message: 'failed to update annotation',
1029
1031
  });
1030
1032
  }
1031
- /**
1032
- * {@inheritDoc @embedpdf/models!PdfEngine.transformPageAnnotation}
1033
- *
1034
- * @public
1035
- */
1036
- transformPageAnnotation(doc, page, annotation, transformation) {
1037
- this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'transformPageAnnotation', doc, page, annotation, transformation);
1038
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
1039
- const ctx = this.cache.getContext(doc.id);
1040
- if (!ctx) {
1041
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1042
- return PdfTaskHelper.reject({
1043
- code: PdfErrorCode.DocNotOpen,
1044
- message: 'document does not open',
1045
- });
1046
- }
1047
- const pageCtx = ctx.acquirePage(page.index);
1048
- const annotationPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
1049
- const rect = {
1050
- origin: {
1051
- x: annotation.rect.origin.x + transformation.offset.x,
1052
- y: annotation.rect.origin.y + transformation.offset.y,
1053
- },
1054
- size: {
1055
- width: annotation.rect.size.width * transformation.scale.width,
1056
- height: annotation.rect.size.height * transformation.scale.height,
1057
- },
1058
- };
1059
- if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotationPtr, rect)) {
1060
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1061
- pageCtx.release();
1062
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1063
- return PdfTaskHelper.reject({
1064
- code: PdfErrorCode.CantSetAnnotRect,
1065
- message: 'can not set the rect of the annotation',
1066
- });
1067
- }
1068
- switch (annotation.type) {
1069
- case PdfAnnotationSubtype.INK:
1070
- {
1071
- if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotationPtr)) {
1072
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1073
- pageCtx.release();
1074
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1075
- return PdfTaskHelper.reject({
1076
- code: PdfErrorCode.CantRemoveInkList,
1077
- message: 'can not set the rect of the annotation',
1078
- });
1079
- }
1080
- const inkList = annotation.inkList.map((inkStroke) => {
1081
- return {
1082
- points: inkStroke.points.map((point) => {
1083
- return {
1084
- x: rect.origin.x +
1085
- (point.x - annotation.rect.origin.x) * transformation.scale.width,
1086
- y: rect.origin.y +
1087
- (point.y - annotation.rect.origin.y) * transformation.scale.height,
1088
- };
1089
- }),
1090
- };
1091
- });
1092
- if (!this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, inkList)) {
1093
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1094
- pageCtx.release();
1095
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1096
- return PdfTaskHelper.reject({
1097
- code: PdfErrorCode.CantAddInkStoke,
1098
- message: 'can not add stroke to the ink list of annotation',
1099
- });
1100
- }
1101
- }
1102
- break;
1103
- }
1104
- this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1105
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1106
- pageCtx.release();
1107
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1108
- return PdfTaskHelper.resolve(true);
1109
- }
1110
1033
  /**
1111
1034
  * {@inheritDoc @embedpdf/models!PdfEngine.removePageAnnotation}
1112
1035
  *
@@ -1727,21 +1650,27 @@ class PdfiumEngine {
1727
1650
  *
1728
1651
  * @private
1729
1652
  */
1730
- addInkStroke(page, pagePtr, annotationPtr, inkList) {
1731
- for (const inkStroke of inkList) {
1732
- const inkPointsCount = inkStroke.points.length;
1733
- const inkPointsPtr = this.malloc(inkPointsCount * 8);
1734
- for (let i = 0; i < inkPointsCount; i++) {
1735
- const point = inkStroke.points[i];
1736
- const { x, y } = this.convertDevicePointToPagePoint(page, point);
1737
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8, x, 'float');
1738
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8 + 4, y, 'float');
1739
- }
1740
- if (this.pdfiumModule.FPDFAnnot_AddInkStroke(annotationPtr, inkPointsPtr, inkPointsCount) === -1) {
1741
- this.free(inkPointsPtr);
1742
- return false;
1743
- }
1744
- this.free(inkPointsPtr);
1653
+ addInkStroke(page, pagePtr, annotationPtr, annotation) {
1654
+ if (!this.setBorderStyle(annotationPtr, PdfAnnotationBorderStyle.SOLID, annotation.strokeWidth)) {
1655
+ return false;
1656
+ }
1657
+ if (!this.setPageAnnoRect(page, pagePtr, annotationPtr, annotation.rect)) {
1658
+ return false;
1659
+ }
1660
+ if (!this.setInkList(page, annotationPtr, annotation.inkList)) {
1661
+ return false;
1662
+ }
1663
+ if (!this.setAnnotString(annotationPtr, 'T', annotation.author || '')) {
1664
+ return false;
1665
+ }
1666
+ if (!this.setAnnotString(annotationPtr, 'M', dateToPdfDate(annotation.modified))) {
1667
+ return false;
1668
+ }
1669
+ if (!this.setAnnotationColor(annotationPtr, {
1670
+ color: annotation.color ?? '#FFFF00',
1671
+ opacity: annotation.opacity ?? 1,
1672
+ }, PdfAnnotationColorType.Color)) {
1673
+ return false;
1745
1674
  }
1746
1675
  return true;
1747
1676
  }
@@ -1754,7 +1683,10 @@ class PdfiumEngine {
1754
1683
  *
1755
1684
  * @private
1756
1685
  */
1757
- addTextMarkupContent(page, annotationPtr, annotation, shouldClearAP = false) {
1686
+ addTextMarkupContent(page, pagePtr, annotationPtr, annotation) {
1687
+ if (!this.setPageAnnoRect(page, pagePtr, annotationPtr, annotation.rect)) {
1688
+ return false;
1689
+ }
1758
1690
  if (!this.syncQuadPointsAnno(page, annotationPtr, annotation.segmentRects)) {
1759
1691
  return false;
1760
1692
  }
@@ -1770,7 +1702,7 @@ class PdfiumEngine {
1770
1702
  if (!this.setAnnotationColor(annotationPtr, {
1771
1703
  color: annotation.color ?? '#FFFF00',
1772
1704
  opacity: annotation.opacity ?? 1,
1773
- }, shouldClearAP, 0)) {
1705
+ }, PdfAnnotationColorType.Color)) {
1774
1706
  return false;
1775
1707
  }
1776
1708
  return true;
@@ -2417,14 +2349,13 @@ class PdfiumEngine {
2417
2349
  *
2418
2350
  * @private
2419
2351
  */
2420
- readAnnotationColor(annotationPtr) {
2352
+ readAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color) {
2421
2353
  const rPtr = this.malloc(4);
2422
2354
  const gPtr = this.malloc(4);
2423
2355
  const bPtr = this.malloc(4);
2424
2356
  const aPtr = this.malloc(4);
2425
2357
  // colourType 0 = "colour" (stroke/fill); other types are interior/border
2426
- const ok = this.pdfiumModule.FPDFAnnot_GetColor(annotationPtr,
2427
- /* colorType = */ 0, rPtr, gPtr, bPtr, aPtr);
2358
+ const ok = this.pdfiumModule.EPDFAnnot_GetColor(annotationPtr, colorType, rPtr, gPtr, bPtr, aPtr);
2428
2359
  let colour;
2429
2360
  if (ok) {
2430
2361
  colour = {
@@ -2441,97 +2372,6 @@ class PdfiumEngine {
2441
2372
  return colour;
2442
2373
  }
2443
2374
  /* --------------------------------------------------------------------------- */
2444
- /**
2445
- * Extract the fill (or, if absent, the stroke) colour from a **path object**
2446
- * inside an appearance stream.
2447
- *
2448
- * Works for simple highlights produced by Chrome, Preview, etc. that paint a
2449
- * single filled rectangle with the desired tint.
2450
- *
2451
- * @param pathPtr - pointer to a `FPDF_PAGEOBJECT` of type **PATH**
2452
- * @returns RGBA tuple or `undefined` when no colour is set on the path
2453
- *
2454
- * @private
2455
- */
2456
- getColorFromPath(pathPtr) {
2457
- const r = this.malloc(4), g = this.malloc(4), b = this.malloc(4), a = this.malloc(4);
2458
- const fillOk = this.pdfiumModule.FPDFPageObj_GetFillColor(pathPtr, r, g, b, a);
2459
- const strokeOk = !fillOk && // try stroke only if fill failed
2460
- this.pdfiumModule.FPDFPageObj_GetStrokeColor(pathPtr, r, g, b, a);
2461
- const ok = fillOk || strokeOk;
2462
- let c;
2463
- if (ok) {
2464
- c = {
2465
- red: this.pdfiumModule.pdfium.getValue(r, 'i32') & 0xff,
2466
- green: this.pdfiumModule.pdfium.getValue(g, 'i32') & 0xff,
2467
- blue: this.pdfiumModule.pdfium.getValue(b, 'i32') & 0xff,
2468
- alpha: this.pdfiumModule.pdfium.getValue(a, 'i32') & 0xff,
2469
- };
2470
- }
2471
- this.free(r);
2472
- this.free(g);
2473
- this.free(b);
2474
- this.free(a);
2475
- return c;
2476
- }
2477
- /* --------------------------------------------------------------------------- */
2478
- /**
2479
- * Recursively walk a page-object tree (PATHs and nested FORM XObjects) until
2480
- * a colour can be extracted.
2481
- *
2482
- * Acrobat often wraps its highlight rectangle in a Form XObject referenced by
2483
- * the "Do" operator, so this function drills down unlimited depth.
2484
- *
2485
- * @param objPtr - pointer to a `FPDF_PAGEOBJECT`
2486
- * @returns First RGBA tint found, or `undefined` if none of the descendants
2487
- * carry an explicit fill/stroke colour
2488
- *
2489
- * @private
2490
- */
2491
- walkPageObjTree(objPtr) {
2492
- const type = this.pdfiumModule.FPDFPageObj_GetType(objPtr);
2493
- if (type === PdfPageObjectType.PATH)
2494
- return this.getColorFromPath(objPtr);
2495
- if (type !== PdfPageObjectType.FORM)
2496
- return undefined;
2497
- const cnt = this.pdfiumModule.FPDFFormObj_CountObjects(objPtr);
2498
- for (let i = 0; i < cnt; i++) {
2499
- const child = this.pdfiumModule.FPDFFormObj_GetObject(objPtr, i);
2500
- if (!child)
2501
- continue;
2502
- const c = this.walkPageObjTree(child);
2503
- if (c)
2504
- return c;
2505
- }
2506
- return undefined;
2507
- }
2508
- /* --------------------------------------------------------------------------- */
2509
- /**
2510
- * Iterate over every top-level object in the annotation's **appearance stream**
2511
- * and invoke {@link walkPageObjTree} to locate a usable tint.
2512
- *
2513
- * Catches:
2514
- * • Simple filled path (Preview, Chrome)
2515
- * • Form XObject containing the path (Acrobat)
2516
- *
2517
- * @param annotPtr - pointer to an `FPDF_ANNOTATION`
2518
- * @returns RGBA tuple or `undefined` when no colour can be resolved from AP
2519
- *
2520
- * @private
2521
- */
2522
- colorFromAppearance(annotPtr) {
2523
- const n = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr);
2524
- for (let i = 0; i < n; i++) {
2525
- const obj = this.pdfiumModule.FPDFAnnot_GetObject(annotPtr, i);
2526
- if (!obj)
2527
- continue;
2528
- const c = this.walkPageObjTree(obj);
2529
- if (c)
2530
- return c;
2531
- }
2532
- return undefined;
2533
- }
2534
- /* --------------------------------------------------------------------------- */
2535
2375
  /**
2536
2376
  * Resolve the visible fill colour for **Highlight / Underline / StrikeOut /
2537
2377
  * Squiggly** markup annotations.
@@ -2547,10 +2387,8 @@ class PdfiumEngine {
2547
2387
  *
2548
2388
  * @private
2549
2389
  */
2550
- resolveAnnotationColor(annotationPtr, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2551
- const pdfColor = this.readAnnotationColor(annotationPtr) ?? // 1 – /C entry
2552
- this.colorFromAppearance(annotationPtr) ?? // 2 – AP stream walk
2553
- fallback; // 3 – default
2390
+ resolveAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2391
+ const pdfColor = this.readAnnotationColor(annotationPtr, colorType) ?? fallback;
2554
2392
  return pdfAlphaColorToWebAlphaColor(pdfColor);
2555
2393
  }
2556
2394
  /**
@@ -2564,14 +2402,115 @@ class PdfiumEngine {
2564
2402
  *
2565
2403
  * @private
2566
2404
  */
2567
- setAnnotationColor(annotationPtr, webAlphaColor, shouldClearAP = false, which = 0) {
2405
+ setAnnotationColor(annotationPtr, webAlphaColor, colorType = PdfAnnotationColorType.Color) {
2568
2406
  const pdfAlphaColor = webAlphaColorToPdfAlphaColor(webAlphaColor);
2569
- if (shouldClearAP) {
2570
- // NULL wide-string → remove the /AP entry
2571
- this.pdfiumModule.FPDFAnnot_SetAP(annotationPtr, AppearanceMode.Normal,
2572
- /* FPDF_WIDESTRING = */ 0);
2407
+ return this.pdfiumModule.EPDFAnnot_SetColor(annotationPtr, colorType, pdfAlphaColor.red & 0xff, pdfAlphaColor.green & 0xff, pdfAlphaColor.blue & 0xff, (pdfAlphaColor.alpha ?? 255) & 0xff);
2408
+ }
2409
+ /**
2410
+ * Border‐style + width helper
2411
+ *
2412
+ * Tries the new PDFium helper `EPDFAnnot_GetBorderStyle()` (patch series
2413
+ * 9 July 2025).
2414
+ *
2415
+ * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2416
+ * @returns `{ ok, style, width }`
2417
+ * • `ok` – `true` when the call succeeded
2418
+ * • `style` – `PdfAnnotationBorderStyle` enum
2419
+ * • `width` – stroke-width in points (defaults to 0 pt)
2420
+ */
2421
+ getBorderStyle(annotationPtr) {
2422
+ /* 1 ── allocate tmp storage for the returned width ─────────────── */
2423
+ const widthPtr = this.malloc(4);
2424
+ let width = 0;
2425
+ let style = PdfAnnotationBorderStyle.UNKNOWN;
2426
+ let ok = false;
2427
+ style = this.pdfiumModule.EPDFAnnot_GetBorderStyle(annotationPtr, widthPtr);
2428
+ width = this.pdfiumModule.pdfium.getValue(widthPtr, 'float');
2429
+ ok = style !== PdfAnnotationBorderStyle.UNKNOWN;
2430
+ this.free(widthPtr);
2431
+ return { ok, style, width };
2432
+ }
2433
+ setBorderStyle(annotationPtr, style, width) {
2434
+ return this.pdfiumModule.EPDFAnnot_SetBorderStyle(annotationPtr, style, width);
2435
+ }
2436
+ /**
2437
+ * Border-effect (“cloudy”) helper
2438
+ *
2439
+ * Calls the new PDFium function `EPDFAnnot_GetBorderEffect()` (July 2025).
2440
+ *
2441
+ * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2442
+ * @returns `{ ok, intensity }`
2443
+ * • `ok` – `true` when the annotation *does* have a
2444
+ * valid cloudy-border effect
2445
+ * • `intensity` – radius/intensity value (0 when `ok` is false)
2446
+ */
2447
+ getBorderEffect(annotationPtr) {
2448
+ const intensityPtr = this.malloc(4);
2449
+ const ok = !!this.pdfiumModule.EPDFAnnot_GetBorderEffect(annotationPtr, intensityPtr);
2450
+ const intensity = ok ? this.pdfiumModule.pdfium.getValue(intensityPtr, 'float') : 0;
2451
+ this.free(intensityPtr);
2452
+ return { ok, intensity };
2453
+ }
2454
+ /**
2455
+ * Rectangle-differences helper ( /RD array on Square / Circle annots )
2456
+ *
2457
+ * Calls `EPDFAnnot_GetRectangleDifferences()` introduced in July 2025.
2458
+ *
2459
+ * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2460
+ * @returns `{ ok, left, top, right, bottom }`
2461
+ * • `ok` – `true` when the annotation *has* an /RD entry
2462
+ * • the four floats are 0 when `ok` is false
2463
+ */
2464
+ getRectangleDifferences(annotationPtr) {
2465
+ /* tmp storage ─────────────────────────────────────────── */
2466
+ const lPtr = this.malloc(4);
2467
+ const tPtr = this.malloc(4);
2468
+ const rPtr = this.malloc(4);
2469
+ const bPtr = this.malloc(4);
2470
+ const ok = !!this.pdfiumModule.EPDFAnnot_GetRectangleDifferences(annotationPtr, lPtr, tPtr, rPtr, bPtr);
2471
+ const pdf = this.pdfiumModule.pdfium;
2472
+ const left = pdf.getValue(lPtr, 'float');
2473
+ const top = pdf.getValue(tPtr, 'float');
2474
+ const right = pdf.getValue(rPtr, 'float');
2475
+ const bottom = pdf.getValue(bPtr, 'float');
2476
+ /* cleanup ─────────────────────────────────────────────── */
2477
+ this.free(lPtr);
2478
+ this.free(tPtr);
2479
+ this.free(rPtr);
2480
+ this.free(bPtr);
2481
+ return { ok, left, top, right, bottom };
2482
+ }
2483
+ /**
2484
+ * Dash-pattern helper ( /BS → /D array, dashed borders only )
2485
+ *
2486
+ * Uses the two new PDFium helpers:
2487
+ * • `EPDFAnnot_GetBorderDashPatternCount`
2488
+ * • `EPDFAnnot_GetBorderDashPattern`
2489
+ *
2490
+ * @param annotationPtr pointer to an `FPDF_ANNOTATION`
2491
+ * @returns `{ ok, pattern }`
2492
+ * • `ok` – `true` when the annot is dashed *and* the array
2493
+ * was retrieved successfully
2494
+ * • `pattern` – numeric array of dash/space lengths (empty when `ok` is false)
2495
+ */
2496
+ getBorderDashPattern(annotationPtr) {
2497
+ const count = this.pdfiumModule.EPDFAnnot_GetBorderDashPatternCount(annotationPtr);
2498
+ if (count === 0) {
2499
+ return { ok: false, pattern: [] };
2500
+ }
2501
+ /* allocate `count` floats on the WASM heap */
2502
+ const arrPtr = this.malloc(4 * count);
2503
+ const okNative = !!this.pdfiumModule.EPDFAnnot_GetBorderDashPattern(annotationPtr, arrPtr, count);
2504
+ /* copy out */
2505
+ const pattern = [];
2506
+ if (okNative) {
2507
+ const pdf = this.pdfiumModule.pdfium;
2508
+ for (let i = 0; i < count; i++) {
2509
+ pattern.push(pdf.getValue(arrPtr + 4 * i, 'float'));
2510
+ }
2573
2511
  }
2574
- return this.pdfiumModule.FPDFAnnot_SetColor(annotationPtr, which, pdfAlphaColor.red & 0xff, pdfAlphaColor.green & 0xff, pdfAlphaColor.blue & 0xff, (pdfAlphaColor.alpha ?? 255) & 0xff);
2512
+ this.free(arrPtr);
2513
+ return { ok: okNative, pattern };
2575
2514
  }
2576
2515
  /**
2577
2516
  * Read `/QuadPoints` from any annotation and convert each quadrilateral to
@@ -2670,6 +2609,62 @@ class PdfiumEngine {
2670
2609
  this.free(buf);
2671
2610
  return true;
2672
2611
  }
2612
+ /**
2613
+ * Read ink list from annotation
2614
+ * @param page - logical page info object (`PdfPageObject`)
2615
+ * @param annotationPtr - pointer to the annotation whose ink list is needed
2616
+ * @returns ink list
2617
+ */
2618
+ getInkList(page, annotationPtr) {
2619
+ const inkList = [];
2620
+ const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2621
+ for (let i = 0; i < count; i++) {
2622
+ const points = [];
2623
+ const pointsCount = this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, 0, 0);
2624
+ if (pointsCount > 0) {
2625
+ const pointMemorySize = 8;
2626
+ const pointsPtr = this.malloc(pointsCount * pointMemorySize);
2627
+ this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, pointsPtr, pointsCount);
2628
+ for (let j = 0; j < pointsCount; j++) {
2629
+ const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8, 'float');
2630
+ const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8 + 4, 'float');
2631
+ const { x, y } = this.convertPagePointToDevicePoint(page, {
2632
+ x: pointX,
2633
+ y: pointY,
2634
+ });
2635
+ points.push({ x, y });
2636
+ }
2637
+ this.free(pointsPtr);
2638
+ }
2639
+ inkList.push({ points });
2640
+ }
2641
+ return inkList;
2642
+ }
2643
+ /**
2644
+ * Add ink list to annotation
2645
+ * @param page - logical page info object (`PdfPageObject`)
2646
+ * @param annotationPtr - pointer to the annotation whose ink list is needed
2647
+ * @param annotation - annotation object (`PdfInkAnnoObject`)
2648
+ * @returns `true` if the operation was successful
2649
+ */
2650
+ setInkList(page, annotationPtr, inkList) {
2651
+ for (const inkStroke of inkList) {
2652
+ const inkPointsCount = inkStroke.points.length;
2653
+ const inkPointsPtr = this.malloc(inkPointsCount * 8);
2654
+ for (let i = 0; i < inkPointsCount; i++) {
2655
+ const point = inkStroke.points[i];
2656
+ const { x, y } = this.convertDevicePointToPagePoint(page, point);
2657
+ this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8, x, 'float');
2658
+ this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8 + 4, y, 'float');
2659
+ }
2660
+ if (this.pdfiumModule.FPDFAnnot_AddInkStroke(annotationPtr, inkPointsPtr, inkPointsCount) === -1) {
2661
+ this.free(inkPointsPtr);
2662
+ return false;
2663
+ }
2664
+ this.free(inkPointsPtr);
2665
+ }
2666
+ return true;
2667
+ }
2673
2668
  /**
2674
2669
  * Read pdf text annotation
2675
2670
  * @param page - pdf page infor
@@ -2846,32 +2841,15 @@ class PdfiumEngine {
2846
2841
  const author = this.getAnnotString(annotationPtr, 'T');
2847
2842
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2848
2843
  const modified = pdfDateToDate(modifiedRaw);
2849
- const inkList = [];
2850
- const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2851
- for (let i = 0; i < count; i++) {
2852
- const points = [];
2853
- const pointsCount = this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, 0, 0);
2854
- if (pointsCount > 0) {
2855
- const pointMemorySize = 8;
2856
- const pointsPtr = this.malloc(pointsCount * pointMemorySize);
2857
- this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, pointsPtr, pointsCount);
2858
- for (let j = 0; j < pointsCount; j++) {
2859
- const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8, 'float');
2860
- const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8 + 4, 'float');
2861
- const { x, y } = this.convertPagePointToDevicePoint(page, {
2862
- x: pointX,
2863
- y: pointY,
2864
- });
2865
- points.push({ x, y });
2866
- }
2867
- this.free(pointsPtr);
2868
- }
2869
- inkList.push({ points });
2870
- }
2844
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2845
+ const { width: strokeWidth } = this.getBorderStyle(annotationPtr);
2846
+ const inkList = this.getInkList(page, annotationPtr);
2871
2847
  return {
2872
2848
  pageIndex: page.index,
2873
2849
  id: index,
2874
2850
  type: PdfAnnotationSubtype.INK,
2851
+ ...webAlphaColor,
2852
+ strokeWidth,
2875
2853
  rect,
2876
2854
  inkList,
2877
2855
  author,
@@ -3325,6 +3303,39 @@ class PdfiumEngine {
3325
3303
  this.free(matrixPtr);
3326
3304
  return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
3327
3305
  }
3306
+ /**
3307
+ * Return the stroke-width declared in the annotation’s /Border or /BS entry.
3308
+ * Falls back to 1 pt when nothing is defined.
3309
+ *
3310
+ * @param annotationPtr - pointer to pdf annotation
3311
+ * @returns stroke-width
3312
+ *
3313
+ * @private
3314
+ */
3315
+ getStrokeWidth(annotationPtr) {
3316
+ // FPDFAnnot_GetBorder(annot, &hRadius, &vRadius, &borderWidth)
3317
+ const hPtr = this.malloc(4);
3318
+ const vPtr = this.malloc(4);
3319
+ const wPtr = this.malloc(4);
3320
+ const ok = this.pdfiumModule.FPDFAnnot_GetBorder(annotationPtr, hPtr, vPtr, wPtr);
3321
+ const width = ok ? this.pdfiumModule.pdfium.getValue(wPtr, 'float') : 1; // default 1 pt
3322
+ this.free(hPtr);
3323
+ this.free(vPtr);
3324
+ this.free(wPtr);
3325
+ return width;
3326
+ }
3327
+ /**
3328
+ * Fetches the `/F` flag bit-field from an annotation.
3329
+ *
3330
+ * @param annotationPtr pointer to an `FPDF_ANNOTATION`
3331
+ * @returns `{ raw, flags }`
3332
+ * • `raw` – the 32-bit integer returned by PDFium
3333
+ * • `flags` – object with individual booleans
3334
+ */
3335
+ getAnnotationFlags(annotationPtr) {
3336
+ const rawFlags = this.pdfiumModule.FPDFAnnot_GetFlags(annotationPtr); // number
3337
+ return flagsToNames(rawFlags);
3338
+ }
3328
3339
  /**
3329
3340
  * Read circle annotation
3330
3341
  * @param page - pdf page infor
@@ -3336,18 +3347,51 @@ class PdfiumEngine {
3336
3347
  * @private
3337
3348
  */
3338
3349
  readPdfCircleAnno(page, pagePtr, annotationPtr, index) {
3350
+ const flags = this.getAnnotationFlags(annotationPtr);
3339
3351
  const pageRect = this.readPageAnnoRect(annotationPtr);
3340
3352
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3341
3353
  const author = this.getAnnotString(annotationPtr, 'T');
3342
3354
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3343
3355
  const modified = pdfDateToDate(modifiedRaw);
3356
+ const { color, opacity } = this.resolveAnnotationColor(annotationPtr, PdfAnnotationColorType.InteriorColor);
3357
+ const { color: strokeColor } = this.resolveAnnotationColor(annotationPtr);
3358
+ let { style: strokeStyle, width: strokeWidth } = this.getBorderStyle(annotationPtr);
3359
+ let cloudyBorderIntensity;
3360
+ let cloudyBorderInset;
3361
+ if (strokeStyle === PdfAnnotationBorderStyle.CLOUDY ||
3362
+ strokeStyle === PdfAnnotationBorderStyle.UNKNOWN) {
3363
+ const { ok: hasEffect, intensity } = this.getBorderEffect(annotationPtr);
3364
+ if (hasEffect) {
3365
+ cloudyBorderIntensity = intensity;
3366
+ strokeStyle = PdfAnnotationBorderStyle.CLOUDY;
3367
+ const { ok: hasInset, left, top, right, bottom, } = this.getRectangleDifferences(annotationPtr);
3368
+ if (hasInset)
3369
+ cloudyBorderInset = [left, top, right, bottom];
3370
+ }
3371
+ }
3372
+ let strokeDashArray;
3373
+ if (strokeStyle === PdfAnnotationBorderStyle.DASHED) {
3374
+ const { ok, pattern } = this.getBorderDashPattern(annotationPtr);
3375
+ if (ok) {
3376
+ strokeDashArray = pattern;
3377
+ }
3378
+ }
3344
3379
  return {
3345
3380
  pageIndex: page.index,
3346
3381
  id: index,
3347
3382
  type: PdfAnnotationSubtype.CIRCLE,
3383
+ flags,
3384
+ color,
3385
+ opacity,
3386
+ strokeWidth,
3387
+ strokeColor,
3388
+ strokeStyle,
3348
3389
  rect,
3349
3390
  author,
3350
3391
  modified,
3392
+ ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3393
+ ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3394
+ ...(strokeDashArray !== undefined && { strokeDashArray }),
3351
3395
  };
3352
3396
  }
3353
3397
  /**
@@ -3361,18 +3405,51 @@ class PdfiumEngine {
3361
3405
  * @private
3362
3406
  */
3363
3407
  readPdfSquareAnno(page, pagePtr, annotationPtr, index) {
3408
+ const flags = this.getAnnotationFlags(annotationPtr);
3364
3409
  const pageRect = this.readPageAnnoRect(annotationPtr);
3365
3410
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3366
3411
  const author = this.getAnnotString(annotationPtr, 'T');
3367
3412
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3368
3413
  const modified = pdfDateToDate(modifiedRaw);
3414
+ const { color, opacity } = this.resolveAnnotationColor(annotationPtr, PdfAnnotationColorType.InteriorColor);
3415
+ const { color: strokeColor } = this.resolveAnnotationColor(annotationPtr);
3416
+ let { style: strokeStyle, width: strokeWidth } = this.getBorderStyle(annotationPtr);
3417
+ let cloudyBorderIntensity;
3418
+ let cloudyBorderInset;
3419
+ if (strokeStyle === PdfAnnotationBorderStyle.CLOUDY ||
3420
+ strokeStyle === PdfAnnotationBorderStyle.UNKNOWN) {
3421
+ const { ok: hasEffect, intensity } = this.getBorderEffect(annotationPtr);
3422
+ if (hasEffect) {
3423
+ cloudyBorderIntensity = intensity;
3424
+ strokeStyle = PdfAnnotationBorderStyle.CLOUDY;
3425
+ const { ok: hasInset, left, top, right, bottom, } = this.getRectangleDifferences(annotationPtr);
3426
+ if (hasInset)
3427
+ cloudyBorderInset = [left, top, right, bottom];
3428
+ }
3429
+ }
3430
+ let strokeDashArray;
3431
+ if (strokeStyle === PdfAnnotationBorderStyle.DASHED) {
3432
+ const { ok, pattern } = this.getBorderDashPattern(annotationPtr);
3433
+ if (ok) {
3434
+ strokeDashArray = pattern;
3435
+ }
3436
+ }
3369
3437
  return {
3370
3438
  pageIndex: page.index,
3371
3439
  id: index,
3372
3440
  type: PdfAnnotationSubtype.SQUARE,
3441
+ flags,
3442
+ color,
3443
+ opacity,
3444
+ strokeColor,
3445
+ strokeWidth,
3446
+ strokeStyle,
3373
3447
  rect,
3374
3448
  author,
3375
3449
  modified,
3450
+ ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3451
+ ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3452
+ ...(strokeDashArray !== undefined && { strokeDashArray }),
3376
3453
  };
3377
3454
  }
3378
3455
  /**
@@ -3558,6 +3635,81 @@ class PdfiumEngine {
3558
3635
  options,
3559
3636
  };
3560
3637
  }
3638
+ /**
3639
+ * {@inheritDoc @embedpdf/models!PdfEngine.renderAnnotation}
3640
+ *
3641
+ * @public
3642
+ */
3643
+ renderAnnotation(doc, page, annotation, scaleFactor, rotation, dpr = 1, // device-pixel-ratio (canvas)
3644
+ mode = AppearanceMode.Normal, imageType = 'image/webp') {
3645
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'renderAnnotation', doc, page, annotation, scaleFactor, rotation, dpr, mode, imageType);
3646
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderAnnotation`, 'Begin', `${doc.id}-${page.index}-${annotation.id}`);
3647
+ const task = new Task();
3648
+ const ctx = this.cache.getContext(doc.id);
3649
+ if (!ctx) {
3650
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3651
+ return PdfTaskHelper.reject({
3652
+ code: PdfErrorCode.DocNotOpen,
3653
+ message: 'document does not open',
3654
+ });
3655
+ }
3656
+ /* ── 1. grab native handles ───────────────────────────────────────── */
3657
+ const pageCtx = ctx.acquirePage(page.index);
3658
+ const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
3659
+ if (!annotPtr) {
3660
+ pageCtx.release();
3661
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3662
+ return PdfTaskHelper.reject({
3663
+ code: PdfErrorCode.NotFound,
3664
+ message: 'annotation not found',
3665
+ });
3666
+ }
3667
+ const finalScale = scaleFactor * dpr;
3668
+ /* ── 2. decide bitmap size (integer pixels) ──────────────────────── */
3669
+ const annotRect = annotation.rect;
3670
+ const bitmapRect = toIntRect(transformRect(page.size, annotRect, rotation, finalScale));
3671
+ const format = BitmapFormat.Bitmap_BGRA;
3672
+ const bytesPerPixel = 4;
3673
+ const bitmapHeapLength = bitmapRect.size.width * bitmapRect.size.height * bytesPerPixel;
3674
+ const bitmapHeapPtr = this.malloc(bitmapHeapLength);
3675
+ const bitmapPtr = this.pdfiumModule.FPDFBitmap_CreateEx(bitmapRect.size.width, bitmapRect.size.height, format, bitmapHeapPtr, bitmapRect.size.width * bytesPerPixel);
3676
+ this.pdfiumModule.FPDFBitmap_FillRect(bitmapPtr, 0, 0, bitmapRect.size.width, bitmapRect.size.height, 0x00000000);
3677
+ const matrix = makeMatrix(annotation.rect, rotation, finalScale);
3678
+ // Allocate memory for the matrix on the wasm heap and write to it
3679
+ const matrixSize = 6 * 4;
3680
+ const matrixPtr = this.malloc(matrixSize);
3681
+ const matrixView = new Float32Array(this.pdfiumModule.pdfium.HEAPF32.buffer, matrixPtr, 6);
3682
+ matrixView.set([matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f]);
3683
+ /* ── 5. call the native helper with the new matrix ───────────────── */
3684
+ const FLAGS = RenderFlag.REVERSE_BYTE_ORDER;
3685
+ const ok = !!this.pdfiumModule.EPDF_RenderAnnotBitmap(bitmapPtr, pageCtx.pagePtr, annotPtr, mode, matrixPtr, FLAGS);
3686
+ /* ── 6. tear down native resources ───────────────────────────────── */
3687
+ this.free(matrixPtr); // Free the matrix memory
3688
+ this.pdfiumModule.FPDFBitmap_Destroy(bitmapPtr);
3689
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
3690
+ pageCtx.release();
3691
+ if (!ok) {
3692
+ this.free(bitmapHeapPtr);
3693
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3694
+ return PdfTaskHelper.reject({
3695
+ code: PdfErrorCode.Unknown,
3696
+ message: 'EPDF_RenderAnnotBitmap failed',
3697
+ });
3698
+ }
3699
+ /* ── 6. copy out + convert to Blob (reuse existing converter) ─────── */
3700
+ const data = this.pdfiumModule.pdfium.HEAPU8.subarray(bitmapHeapPtr, bitmapHeapPtr + bitmapHeapLength);
3701
+ const imageData = {
3702
+ data: new Uint8ClampedArray(data),
3703
+ width: bitmapRect.size.width,
3704
+ height: bitmapRect.size.height,
3705
+ };
3706
+ this.free(bitmapHeapPtr);
3707
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `RenderAnnotation`, 'End', `${doc.id}-${page.index}-${annotation.id}`);
3708
+ this.imageDataConverter(imageData, imageType)
3709
+ .then((blob) => task.resolve(blob))
3710
+ .catch((err) => task.reject({ code: PdfErrorCode.Unknown, message: String(err) }));
3711
+ return task;
3712
+ }
3561
3713
  /**
3562
3714
  * render rectangle of pdf page to image
3563
3715
  * @param docPtr - pointer to pdf document object
@@ -3928,12 +4080,7 @@ class PdfiumEngine {
3928
4080
  task.resolve(false);
3929
4081
  return task;
3930
4082
  }
3931
- /* 2 ── wipe AP if it's a simple markup annotation ─────────────────────── */
3932
- const shouldClearAP = annotation.type === PdfAnnotationSubtype.HIGHLIGHT ||
3933
- annotation.type === PdfAnnotationSubtype.UNDERLINE ||
3934
- annotation.type === PdfAnnotationSubtype.STRIKEOUT ||
3935
- annotation.type === PdfAnnotationSubtype.SQUIGGLY;
3936
- const ok = this.setAnnotationColor(annotPtr, color, shouldClearAP, which);
4083
+ const ok = this.setAnnotationColor(annotPtr, color, which);
3937
4084
  /* 4 ── regenerate appearance & clean-up ───────────────────────────────── */
3938
4085
  if (ok) {
3939
4086
  this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
@@ -4343,6 +4490,9 @@ class EngineRunner {
4343
4490
  case 'renderPageRect':
4344
4491
  task = this.engine[name](...args);
4345
4492
  break;
4493
+ case 'renderAnnotation':
4494
+ task = this.engine[name](...args);
4495
+ break;
4346
4496
  case 'renderThumbnail':
4347
4497
  task = this.engine[name](...args);
4348
4498
  break;
@@ -4358,9 +4508,6 @@ class EngineRunner {
4358
4508
  case 'updatePageAnnotation':
4359
4509
  task = this.engine[name](...args);
4360
4510
  break;
4361
- case 'transformPageAnnotation':
4362
- task = this.engine[name](...args);
4363
- break;
4364
4511
  case 'removePageAnnotation':
4365
4512
  task = this.engine[name](...args);
4366
4513
  break;