@embedpdf/engines 1.0.6 → 1.0.8

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, PdfPageObjectType, PdfAnnotationObjectStatus, quadToRect, PDF_FORM_FIELD_TYPE, toIntRect, transformRect, toIntSize, transformSize, PdfActionType, PdfZoomMode, AppearanceMode, MatchFlag } 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,11 +910,17 @@ 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);
917
917
  break;
918
+ case PdfAnnotationSubtype.UNDERLINE:
919
+ case PdfAnnotationSubtype.STRIKEOUT:
920
+ case PdfAnnotationSubtype.SQUIGGLY:
921
+ case PdfAnnotationSubtype.HIGHLIGHT:
922
+ isSucceed = this.addTextMarkupContent(page, pageCtx.pagePtr, annotationPtr, annotation);
923
+ break;
918
924
  }
919
925
  if (!isSucceed) {
920
926
  this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotationPtr);
@@ -925,90 +931,104 @@ class PdfiumEngine {
925
931
  message: 'can not add content of the annotation',
926
932
  });
927
933
  }
934
+ this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotationPtr);
928
935
  this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
936
+ const annotId = this.pdfiumModule.FPDFPage_GetAnnotIndex(pageCtx.pagePtr, annotationPtr);
929
937
  this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
930
938
  pageCtx.release();
931
939
  this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
932
- return PdfTaskHelper.resolve(true);
940
+ return annotId >= 0
941
+ ? PdfTaskHelper.resolve(annotId)
942
+ : PdfTaskHelper.reject({
943
+ code: PdfErrorCode.CantCreateAnnot,
944
+ message: 'annotation created but index could not be determined',
945
+ });
933
946
  }
934
947
  /**
935
- * {@inheritDoc @embedpdf/models!PdfEngine.transformPageAnnotation}
948
+ * Update an existing page annotation in-place
936
949
  *
937
- * @public
950
+ * Locates the annot by page-local index (`annotation.id`)
951
+ * • Re-writes its /Rect and type-specific payload
952
+ * • Calls FPDFPage_GenerateContent so the new appearance is rendered
953
+ *
954
+ * @returns PdfTask<boolean> – true on success
938
955
  */
939
- transformPageAnnotation(doc, page, annotation, transformation) {
940
- this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'transformPageAnnotation', doc, page, annotation, transformation);
941
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'Begin', `${doc.id}-${page.index}`);
956
+ updatePageAnnotation(doc, page, annotation) {
957
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'updatePageAnnotation', doc, page, annotation);
958
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'UpdatePageAnnotation', 'Begin', `${doc.id}-${page.index}`);
942
959
  const ctx = this.cache.getContext(doc.id);
943
960
  if (!ctx) {
944
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
961
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
945
962
  return PdfTaskHelper.reject({
946
963
  code: PdfErrorCode.DocNotOpen,
947
964
  message: 'document does not open',
948
965
  });
949
966
  }
950
967
  const pageCtx = ctx.acquirePage(page.index);
951
- const annotationPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
952
- const rect = {
953
- origin: {
954
- x: annotation.rect.origin.x + transformation.offset.x,
955
- y: annotation.rect.origin.y + transformation.offset.y,
956
- },
957
- size: {
958
- width: annotation.rect.size.width * transformation.scale.width,
959
- height: annotation.rect.size.height * transformation.scale.height,
960
- },
961
- };
962
- if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotationPtr, rect)) {
963
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
968
+ const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
969
+ if (!annotPtr) {
970
+ pageCtx.release();
971
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
972
+ return PdfTaskHelper.reject({ code: PdfErrorCode.NotFound, message: 'annotation not found' });
973
+ }
974
+ /* 1 ── (re)set bounding-box ────────────────────────────────────────────── */
975
+ if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotPtr, annotation.rect)) {
976
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
964
977
  pageCtx.release();
965
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
978
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
966
979
  return PdfTaskHelper.reject({
967
980
  code: PdfErrorCode.CantSetAnnotRect,
968
- message: 'can not set the rect of the annotation',
981
+ message: 'failed to move annotation',
969
982
  });
970
983
  }
984
+ /* 2 ── wipe previous payload and rebuild fresh one ─────────────────────── */
985
+ let ok = false;
971
986
  switch (annotation.type) {
972
- case PdfAnnotationSubtype.INK:
973
- {
974
- if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotationPtr)) {
975
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
976
- pageCtx.release();
977
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
978
- return PdfTaskHelper.reject({
979
- code: PdfErrorCode.CantRemoveInkList,
980
- message: 'can not set the rect of the annotation',
981
- });
982
- }
983
- const inkList = annotation.inkList.map((inkStroke) => {
984
- return {
985
- points: inkStroke.points.map((point) => {
986
- return {
987
- x: rect.origin.x +
988
- (point.x - annotation.rect.origin.x) * transformation.scale.width,
989
- y: rect.origin.y +
990
- (point.y - annotation.rect.origin.y) * transformation.scale.height,
991
- };
992
- }),
993
- };
994
- });
995
- if (!this.addInkStroke(page, pageCtx.pagePtr, annotationPtr, inkList)) {
996
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
997
- pageCtx.release();
998
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
999
- return PdfTaskHelper.reject({
1000
- code: PdfErrorCode.CantAddInkStoke,
1001
- message: 'can not add stroke to the ink list of annotation',
1002
- });
1003
- }
987
+ /* ── Ink ─────────────────────────────────────────────────────────────── */
988
+ case PdfAnnotationSubtype.INK: {
989
+ /* clear every existing stroke first */
990
+ if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotPtr))
991
+ break;
992
+ ok = this.addInkStroke(page, pageCtx.pagePtr, annotPtr, annotation);
993
+ break;
994
+ }
995
+ /* ── Stamp ───────────────────────────────────────────────────────────── */
996
+ case PdfAnnotationSubtype.STAMP: {
997
+ /* drop every page-object inside the annot */
998
+ for (let i = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr) - 1; i >= 0; i--) {
999
+ this.pdfiumModule.FPDFAnnot_RemoveObject(annotPtr, i);
1004
1000
  }
1001
+ ok = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotPtr, annotation.rect, annotation.contents);
1002
+ break;
1003
+ }
1004
+ /* ── Text-markup family ──────────────────────────────────────────────── */
1005
+ case PdfAnnotationSubtype.HIGHLIGHT:
1006
+ case PdfAnnotationSubtype.UNDERLINE:
1007
+ case PdfAnnotationSubtype.STRIKEOUT:
1008
+ case PdfAnnotationSubtype.SQUIGGLY: {
1009
+ /* replace quad-points / colour / strings in one go */
1010
+ ok = this.addTextMarkupContent(page, pageCtx.pagePtr, annotPtr, annotation);
1005
1011
  break;
1012
+ }
1013
+ /* ── Unsupported edits – fall through to error ───────────────────────── */
1014
+ default:
1015
+ ok = false;
1006
1016
  }
1007
- this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1008
- this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
1017
+ /* 3 ── regenerate appearance if payload was changed ───────────────────── */
1018
+ if (ok) {
1019
+ this.pdfiumModule.EPDFAnnot_GenerateAppearance(annotPtr);
1020
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1021
+ }
1022
+ /* 4 ── tidy-up native handles ──────────────────────────────────────────── */
1023
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
1009
1024
  pageCtx.release();
1010
- this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, `TransformPageAnnotation`, 'End', `${doc.id}-${page.index}`);
1011
- return PdfTaskHelper.resolve(true);
1025
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
1026
+ return ok
1027
+ ? PdfTaskHelper.resolve(true)
1028
+ : PdfTaskHelper.reject({
1029
+ code: PdfErrorCode.CantSetAnnotContent,
1030
+ message: 'failed to update annotation',
1031
+ });
1012
1032
  }
1013
1033
  /**
1014
1034
  * {@inheritDoc @embedpdf/models!PdfEngine.removePageAnnotation}
@@ -1630,21 +1650,60 @@ class PdfiumEngine {
1630
1650
  *
1631
1651
  * @private
1632
1652
  */
1633
- addInkStroke(page, pagePtr, annotationPtr, inkList) {
1634
- for (const inkStroke of inkList) {
1635
- const inkPointsCount = inkStroke.points.length;
1636
- const inkPointsPtr = this.malloc(inkPointsCount * 8);
1637
- for (let i = 0; i < inkPointsCount; i++) {
1638
- const point = inkStroke.points[i];
1639
- const { x, y } = this.convertDevicePointToPagePoint(page, point);
1640
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8, x, 'float');
1641
- this.pdfiumModule.pdfium.setValue(inkPointsPtr + i * 8 + 4, y, 'float');
1642
- }
1643
- if (this.pdfiumModule.FPDFAnnot_AddInkStroke(annotationPtr, inkPointsPtr, inkPointsCount) === -1) {
1644
- this.free(inkPointsPtr);
1645
- return false;
1646
- }
1647
- 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;
1674
+ }
1675
+ return true;
1676
+ }
1677
+ /**
1678
+ * Add highlight content to annotation
1679
+ * @param page - page info
1680
+ * @param annotationPtr - pointer to highlight annotation
1681
+ * @param annotation - highlight annotation
1682
+ * @returns whether highlight content is added to annotation
1683
+ *
1684
+ * @private
1685
+ */
1686
+ addTextMarkupContent(page, pagePtr, annotationPtr, annotation) {
1687
+ if (!this.setPageAnnoRect(page, pagePtr, annotationPtr, annotation.rect)) {
1688
+ return false;
1689
+ }
1690
+ if (!this.syncQuadPointsAnno(page, annotationPtr, annotation.segmentRects)) {
1691
+ return false;
1692
+ }
1693
+ if (!this.setAnnotString(annotationPtr, 'Contents', annotation.contents ?? '')) {
1694
+ return false;
1695
+ }
1696
+ if (!this.setAnnotString(annotationPtr, 'T', annotation.author || '')) {
1697
+ return false;
1698
+ }
1699
+ if (!this.setAnnotString(annotationPtr, 'M', dateToPdfDate(annotation.modified))) {
1700
+ return false;
1701
+ }
1702
+ if (!this.setAnnotationColor(annotationPtr, {
1703
+ color: annotation.color ?? '#FFFF00',
1704
+ opacity: annotation.opacity ?? 1,
1705
+ }, PdfAnnotationColorType.Color)) {
1706
+ return false;
1648
1707
  }
1649
1708
  return true;
1650
1709
  }
@@ -2270,8 +2329,6 @@ class PdfiumEngine {
2270
2329
  annotation = this.readPdfCaretAnno(page, pageCtx.pagePtr, annotationPtr, index);
2271
2330
  }
2272
2331
  break;
2273
- case PdfAnnotationSubtype.POPUP:
2274
- break;
2275
2332
  default:
2276
2333
  {
2277
2334
  annotation = this.readPdfAnno(page, pageCtx.pagePtr, subType, annotationPtr, index);
@@ -2292,14 +2349,13 @@ class PdfiumEngine {
2292
2349
  *
2293
2350
  * @private
2294
2351
  */
2295
- readAnnotationColor(annotationPtr) {
2352
+ readAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color) {
2296
2353
  const rPtr = this.malloc(4);
2297
2354
  const gPtr = this.malloc(4);
2298
2355
  const bPtr = this.malloc(4);
2299
2356
  const aPtr = this.malloc(4);
2300
2357
  // colourType 0 = "colour" (stroke/fill); other types are interior/border
2301
- const ok = this.pdfiumModule.FPDFAnnot_GetColor(annotationPtr,
2302
- /* colorType = */ 0, rPtr, gPtr, bPtr, aPtr);
2358
+ const ok = this.pdfiumModule.EPDFAnnot_GetColor(annotationPtr, colorType, rPtr, gPtr, bPtr, aPtr);
2303
2359
  let colour;
2304
2360
  if (ok) {
2305
2361
  colour = {
@@ -2317,116 +2373,144 @@ class PdfiumEngine {
2317
2373
  }
2318
2374
  /* --------------------------------------------------------------------------- */
2319
2375
  /**
2320
- * Extract the fill (or, if absent, the stroke) colour from a **path object**
2321
- * inside an appearance stream.
2376
+ * Resolve the visible fill colour for **Highlight / Underline / StrikeOut /
2377
+ * Squiggly** markup annotations.
2322
2378
  *
2323
- * Works for simple highlights produced by Chrome, Preview, etc. that paint a
2324
- * single filled rectangle with the desired tint.
2379
+ * Resolution order (first non-`undefined` wins):
2380
+ * 1. `/C` dictionary entry fast, present in Acrobat / Office PDFs
2381
+ * 2. Appearance-stream objects – drills into paths & nested forms
2382
+ * 3. Hard-coded fallback (Acrobat-style opaque yellow)
2325
2383
  *
2326
- * @param pathPtr - pointer to a `FPDF_PAGEOBJECT` of type **PATH**
2327
- * @returns RGBA tuple or `undefined` when no colour is set on the path
2384
+ * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2385
+ * @param fallback - colour to use when the PDF stores no tint at all
2386
+ * @returns WebAlphaColor with hex color and opacity (0-1)
2328
2387
  *
2329
2388
  * @private
2330
2389
  */
2331
- getColorFromPath(pathPtr) {
2332
- const r = this.malloc(4), g = this.malloc(4), b = this.malloc(4), a = this.malloc(4);
2333
- const fillOk = this.pdfiumModule.FPDFPageObj_GetFillColor(pathPtr, r, g, b, a);
2334
- const strokeOk = !fillOk && // try stroke only if fill failed
2335
- this.pdfiumModule.FPDFPageObj_GetStrokeColor(pathPtr, r, g, b, a);
2336
- const ok = fillOk || strokeOk;
2337
- let c;
2338
- if (ok) {
2339
- c = {
2340
- red: this.pdfiumModule.pdfium.getValue(r, 'i32') & 0xff,
2341
- green: this.pdfiumModule.pdfium.getValue(g, 'i32') & 0xff,
2342
- blue: this.pdfiumModule.pdfium.getValue(b, 'i32') & 0xff,
2343
- alpha: this.pdfiumModule.pdfium.getValue(a, 'i32') & 0xff,
2344
- };
2345
- }
2346
- this.free(r);
2347
- this.free(g);
2348
- this.free(b);
2349
- this.free(a);
2350
- return c;
2390
+ resolveAnnotationColor(annotationPtr, colorType = PdfAnnotationColorType.Color, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2391
+ const pdfColor = this.readAnnotationColor(annotationPtr, colorType) ?? fallback;
2392
+ return pdfAlphaColorToWebAlphaColor(pdfColor);
2351
2393
  }
2352
- /* --------------------------------------------------------------------------- */
2353
2394
  /**
2354
- * Recursively walk a page-object tree (PATHs and nested FORM XObjects) until
2355
- * a colour can be extracted.
2356
- *
2357
- * Acrobat often wraps its highlight rectangle in a Form XObject referenced by
2358
- * the "Do" operator, so this function drills down unlimited depth.
2395
+ * Set the fill/stroke colour for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2359
2396
  *
2360
- * @param objPtr - pointer to a `FPDF_PAGEOBJECT`
2361
- * @returns First RGBA tint found, or `undefined` if none of the descendants
2362
- * carry an explicit fill/stroke colour
2397
+ * @param annotationPtr - pointer to the annotation whose colour is being set
2398
+ * @param webAlphaColor - WebAlphaColor with hex color and opacity (0-1)
2399
+ * @param shouldClearAP - whether to clear the /AP entry
2400
+ * @param which - which colour to set (0 = fill, 1 = stroke)
2401
+ * @returns `true` if the operation was successful
2363
2402
  *
2364
2403
  * @private
2365
2404
  */
2366
- walkPageObjTree(objPtr) {
2367
- const type = this.pdfiumModule.FPDFPageObj_GetType(objPtr);
2368
- if (type === PdfPageObjectType.PATH)
2369
- return this.getColorFromPath(objPtr);
2370
- if (type !== PdfPageObjectType.FORM)
2371
- return undefined;
2372
- const cnt = this.pdfiumModule.FPDFFormObj_CountObjects(objPtr);
2373
- for (let i = 0; i < cnt; i++) {
2374
- const child = this.pdfiumModule.FPDFFormObj_GetObject(objPtr, i);
2375
- if (!child)
2376
- continue;
2377
- const c = this.walkPageObjTree(child);
2378
- if (c)
2379
- return c;
2380
- }
2381
- return undefined;
2405
+ setAnnotationColor(annotationPtr, webAlphaColor, colorType = PdfAnnotationColorType.Color) {
2406
+ const pdfAlphaColor = webAlphaColorToPdfAlphaColor(webAlphaColor);
2407
+ return this.pdfiumModule.EPDFAnnot_SetColor(annotationPtr, colorType, pdfAlphaColor.red & 0xff, pdfAlphaColor.green & 0xff, pdfAlphaColor.blue & 0xff, (pdfAlphaColor.alpha ?? 255) & 0xff);
2382
2408
  }
2383
- /* --------------------------------------------------------------------------- */
2384
2409
  /**
2385
- * Iterate over every top-level object in the annotation's **appearance stream**
2386
- * and invoke {@link walkPageObjTree} to locate a usable tint.
2410
+ * Border‐style + width helper
2387
2411
  *
2388
- * Catches:
2389
- * Simple filled path (Preview, Chrome)
2390
- * • Form XObject containing the path (Acrobat)
2412
+ * Tries the new PDFium helper `EPDFAnnot_GetBorderStyle()` (patch series
2413
+ * 9 July 2025).
2391
2414
  *
2392
- * @param annotPtr - pointer to an `FPDF_ANNOTATION`
2393
- * @returns RGBA tuple or `undefined` when no colour can be resolved from AP
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
2394
2438
  *
2395
- * @private
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)
2396
2446
  */
2397
- colorFromAppearance(annotPtr) {
2398
- const n = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr);
2399
- for (let i = 0; i < n; i++) {
2400
- const obj = this.pdfiumModule.FPDFAnnot_GetObject(annotPtr, i);
2401
- if (!obj)
2402
- continue;
2403
- const c = this.walkPageObjTree(obj);
2404
- if (c)
2405
- return c;
2406
- }
2407
- return undefined;
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 };
2408
2453
  }
2409
- /* --------------------------------------------------------------------------- */
2410
2454
  /**
2411
- * Resolve the visible fill colour for **Highlight / Underline / StrikeOut /
2412
- * Squiggly** markup annotations.
2455
+ * Rectangle-differences helper ( /RD array on Square / Circle annots )
2413
2456
  *
2414
- * Resolution order (first non-`undefined` wins):
2415
- * 1. `/C` dictionary entry – fast, present in Acrobat / Office PDFs
2416
- * 2. Appearance-stream objects – drills into paths & nested forms
2417
- * 3. Hard-coded fallback (Acrobat-style opaque yellow)
2457
+ * Calls `EPDFAnnot_GetRectangleDifferences()` introduced in July 2025.
2418
2458
  *
2419
- * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2420
- * @param fallback - colour to use when the PDF stores no tint at all
2421
- * @returns Guaranteed RGBA tuple (never `undefined`)
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 )
2422
2485
  *
2423
- * @private
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)
2424
2495
  */
2425
- resolveAnnotationColor(annotationPtr, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2426
- return (this.readAnnotationColor(annotationPtr) ?? // 1 – /C entry
2427
- this.colorFromAppearance(annotationPtr) ?? // 2 – AP stream walk
2428
- fallback // 3 default
2429
- );
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
+ }
2511
+ }
2512
+ this.free(arrPtr);
2513
+ return { ok: okNative, pattern };
2430
2514
  }
2431
2515
  /**
2432
2516
  * Read `/QuadPoints` from any annotation and convert each quadrilateral to
@@ -2439,11 +2523,11 @@ class PdfiumEngine {
2439
2523
  *
2440
2524
  * @param page - logical page info object (`PdfPageObject`)
2441
2525
  * @param annotationPtr - pointer to the annotation whose quads are needed
2442
- * @returns Array of `Quad` objects (`[]` if the annotation has no quads)
2526
+ * @returns Array of `Rect` objects (`[]` if the annotation has no quads)
2443
2527
  *
2444
2528
  * @private
2445
2529
  */
2446
- readAnnotationQuads(page, annotationPtr) {
2530
+ getQuadPointsAnno(page, annotationPtr) {
2447
2531
  const quadCount = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotationPtr);
2448
2532
  if (quadCount === 0)
2449
2533
  return [];
@@ -2470,7 +2554,116 @@ class PdfiumEngine {
2470
2554
  }
2471
2555
  this.free(quadPtr);
2472
2556
  }
2473
- return quads;
2557
+ return quads.map(quadToRect);
2558
+ }
2559
+ /**
2560
+ * Set the quadrilaterals for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2561
+ *
2562
+ * @param page - logical page info object (`PdfPageObject`)
2563
+ * @param annotationPtr - pointer to the annotation whose quads are needed
2564
+ * @param rects - array of `Rect` objects (`[]` if the annotation has no quads)
2565
+ * @returns `true` if the operation was successful
2566
+ *
2567
+ * @private
2568
+ */
2569
+ syncQuadPointsAnno(page, annotPtr, rects) {
2570
+ const FS_QUADPOINTSF_SIZE = 8 * 4; // eight floats, 32 bytes
2571
+ const pdf = this.pdfiumModule.pdfium;
2572
+ const count = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotPtr);
2573
+ const buf = this.malloc(FS_QUADPOINTSF_SIZE);
2574
+ /** write one quad into `buf` in annotation space */
2575
+ const writeQuad = (r) => {
2576
+ const q = rectToQuad(r); // TL, TR, BR, BL
2577
+ const p1 = this.convertDevicePointToPagePoint(page, q.p1);
2578
+ const p2 = this.convertDevicePointToPagePoint(page, q.p2);
2579
+ const p3 = this.convertDevicePointToPagePoint(page, q.p3); // BR
2580
+ const p4 = this.convertDevicePointToPagePoint(page, q.p4); // BL
2581
+ // PDF QuadPoints order: BL, BR, TL, TR (bottom-left, bottom-right, top-left, top-right)
2582
+ pdf.setValue(buf + 0, p1.x, 'float'); // BL (bottom-left)
2583
+ pdf.setValue(buf + 4, p1.y, 'float');
2584
+ pdf.setValue(buf + 8, p2.x, 'float'); // BR (bottom-right)
2585
+ pdf.setValue(buf + 12, p2.y, 'float');
2586
+ pdf.setValue(buf + 16, p4.x, 'float'); // TL (top-left)
2587
+ pdf.setValue(buf + 20, p4.y, 'float');
2588
+ pdf.setValue(buf + 24, p3.x, 'float'); // TR (top-right)
2589
+ pdf.setValue(buf + 28, p3.y, 'float');
2590
+ };
2591
+ /* ----------------------------------------------------------------------- */
2592
+ /* 1. overwrite the quads that already exist */
2593
+ const min = Math.min(count, rects.length);
2594
+ for (let i = 0; i < min; i++) {
2595
+ writeQuad(rects[i]);
2596
+ if (!this.pdfiumModule.FPDFAnnot_SetAttachmentPoints(annotPtr, i, buf)) {
2597
+ this.free(buf);
2598
+ return false;
2599
+ }
2600
+ }
2601
+ /* 2. append new quads if rects.length > count */
2602
+ for (let i = count; i < rects.length; i++) {
2603
+ writeQuad(rects[i]);
2604
+ if (!this.pdfiumModule.FPDFAnnot_AppendAttachmentPoints(annotPtr, buf)) {
2605
+ this.free(buf);
2606
+ return false;
2607
+ }
2608
+ }
2609
+ this.free(buf);
2610
+ return true;
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;
2474
2667
  }
2475
2668
  /**
2476
2669
  * Read pdf text annotation
@@ -2483,30 +2676,23 @@ class PdfiumEngine {
2483
2676
  * @private
2484
2677
  */
2485
2678
  readPdfTextAnno(page, pagePtr, annotationPtr, index) {
2486
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2487
2679
  const annoRect = this.readPageAnnoRect(annotationPtr);
2488
2680
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2489
2681
  const author = this.getAnnotString(annotationPtr, 'T');
2490
2682
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2491
- const modified = this.toIsoDate(modifiedRaw);
2683
+ const modified = pdfDateToDate(modifiedRaw);
2492
2684
  const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2493
2685
  const state = this.getAnnotString(annotationPtr, 'State');
2494
2686
  const stateModel = this.getAnnotString(annotationPtr, 'StateModel');
2495
- const color = this.resolveAnnotationColor(annotationPtr);
2687
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2496
2688
  const inReplyToId = this.getInReplyToId(pagePtr, annotationPtr);
2497
- const popup = !inReplyToId
2498
- ? this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index)
2499
- : undefined;
2500
2689
  return {
2501
- status: PdfAnnotationObjectStatus.Committed,
2502
2690
  pageIndex: page.index,
2503
2691
  id: index,
2504
2692
  type: PdfAnnotationSubtype.TEXT,
2505
2693
  contents,
2506
- color,
2694
+ ...webAlphaColor,
2507
2695
  rect,
2508
- popup,
2509
- appearances,
2510
2696
  inReplyToId,
2511
2697
  author,
2512
2698
  modified,
@@ -2525,16 +2711,13 @@ class PdfiumEngine {
2525
2711
  * @private
2526
2712
  */
2527
2713
  readPdfFreeTextAnno(page, pagePtr, annotationPtr, index) {
2528
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2529
2714
  const annoRect = this.readPageAnnoRect(annotationPtr);
2530
2715
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2531
2716
  const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2532
2717
  const author = this.getAnnotString(annotationPtr, 'T');
2533
2718
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2534
- const modified = this.toIsoDate(modifiedRaw);
2535
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2719
+ const modified = pdfDateToDate(modifiedRaw);
2536
2720
  return {
2537
- status: PdfAnnotationObjectStatus.Committed,
2538
2721
  pageIndex: page.index,
2539
2722
  id: index,
2540
2723
  type: PdfAnnotationSubtype.FREETEXT,
@@ -2542,8 +2725,6 @@ class PdfiumEngine {
2542
2725
  author,
2543
2726
  modified,
2544
2727
  rect,
2545
- popup,
2546
- appearances,
2547
2728
  };
2548
2729
  }
2549
2730
  /**
@@ -2563,13 +2744,12 @@ class PdfiumEngine {
2563
2744
  if (!linkPtr) {
2564
2745
  return;
2565
2746
  }
2566
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2567
2747
  const annoRect = this.readPageAnnoRect(annotationPtr);
2568
2748
  const { left, top, right, bottom } = annoRect;
2569
2749
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2570
2750
  const author = this.getAnnotString(annotationPtr, 'T');
2571
2751
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2572
- const modified = this.toIsoDate(modifiedRaw);
2752
+ const modified = pdfDateToDate(modifiedRaw);
2573
2753
  const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
2574
2754
  const bytesCount = (utf16Length + 1) * 2; // include NIL
2575
2755
  const textBufferPtr = this.malloc(bytesCount);
@@ -2581,17 +2761,13 @@ class PdfiumEngine {
2581
2761
  }, () => {
2582
2762
  return this.pdfiumModule.FPDFLink_GetDest(docPtr, linkPtr);
2583
2763
  });
2584
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2585
2764
  return {
2586
- status: PdfAnnotationObjectStatus.Committed,
2587
2765
  pageIndex: page.index,
2588
2766
  id: index,
2589
2767
  type: PdfAnnotationSubtype.LINK,
2590
2768
  text,
2591
2769
  target,
2592
2770
  rect,
2593
- popup,
2594
- appearances,
2595
2771
  author,
2596
2772
  modified,
2597
2773
  };
@@ -2608,23 +2784,18 @@ class PdfiumEngine {
2608
2784
  * @private
2609
2785
  */
2610
2786
  readPdfWidgetAnno(page, pagePtr, annotationPtr, formHandle, index) {
2611
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2612
2787
  const pageRect = this.readPageAnnoRect(annotationPtr);
2613
2788
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2614
2789
  const author = this.getAnnotString(annotationPtr, 'T');
2615
2790
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2616
- const modified = this.toIsoDate(modifiedRaw);
2617
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2791
+ const modified = pdfDateToDate(modifiedRaw);
2618
2792
  const field = this.readPdfWidgetAnnoField(formHandle, annotationPtr);
2619
2793
  return {
2620
- status: PdfAnnotationObjectStatus.Committed,
2621
2794
  pageIndex: page.index,
2622
2795
  id: index,
2623
2796
  type: PdfAnnotationSubtype.WIDGET,
2624
2797
  rect,
2625
2798
  field,
2626
- popup,
2627
- appearances,
2628
2799
  author,
2629
2800
  modified,
2630
2801
  };
@@ -2640,21 +2811,16 @@ class PdfiumEngine {
2640
2811
  * @private
2641
2812
  */
2642
2813
  readPdfFileAttachmentAnno(page, pagePtr, annotationPtr, index) {
2643
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2644
2814
  const pageRect = this.readPageAnnoRect(annotationPtr);
2645
2815
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2646
2816
  const author = this.getAnnotString(annotationPtr, 'T');
2647
2817
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2648
- const modified = this.toIsoDate(modifiedRaw);
2649
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2818
+ const modified = pdfDateToDate(modifiedRaw);
2650
2819
  return {
2651
- status: PdfAnnotationObjectStatus.Committed,
2652
2820
  pageIndex: page.index,
2653
2821
  id: index,
2654
2822
  type: PdfAnnotationSubtype.FILEATTACHMENT,
2655
2823
  rect,
2656
- popup,
2657
- appearances,
2658
2824
  author,
2659
2825
  modified,
2660
2826
  };
@@ -2670,44 +2836,22 @@ class PdfiumEngine {
2670
2836
  * @private
2671
2837
  */
2672
2838
  readPdfInkAnno(page, pagePtr, annotationPtr, index) {
2673
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2674
2839
  const pageRect = this.readPageAnnoRect(annotationPtr);
2675
2840
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2676
2841
  const author = this.getAnnotString(annotationPtr, 'T');
2677
2842
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2678
- const modified = this.toIsoDate(modifiedRaw);
2679
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2680
- const inkList = [];
2681
- const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2682
- for (let i = 0; i < count; i++) {
2683
- const points = [];
2684
- const pointsCount = this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, 0, 0);
2685
- if (pointsCount > 0) {
2686
- const pointMemorySize = 8;
2687
- const pointsPtr = this.malloc(pointsCount * pointMemorySize);
2688
- this.pdfiumModule.FPDFAnnot_GetInkListPath(annotationPtr, i, pointsPtr, pointsCount);
2689
- for (let j = 0; j < pointsCount; j++) {
2690
- const pointX = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8, 'float');
2691
- const pointY = this.pdfiumModule.pdfium.getValue(pointsPtr + j * 8 + 4, 'float');
2692
- const { x, y } = this.convertPagePointToDevicePoint(page, {
2693
- x: pointX,
2694
- y: pointY,
2695
- });
2696
- points.push({ x, y });
2697
- }
2698
- this.free(pointsPtr);
2699
- }
2700
- inkList.push({ points });
2701
- }
2843
+ const modified = pdfDateToDate(modifiedRaw);
2844
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2845
+ const { width: strokeWidth } = this.getBorderStyle(annotationPtr);
2846
+ const inkList = this.getInkList(page, annotationPtr);
2702
2847
  return {
2703
- status: PdfAnnotationObjectStatus.Committed,
2704
2848
  pageIndex: page.index,
2705
2849
  id: index,
2706
2850
  type: PdfAnnotationSubtype.INK,
2851
+ ...webAlphaColor,
2852
+ strokeWidth,
2707
2853
  rect,
2708
- popup,
2709
2854
  inkList,
2710
- appearances,
2711
2855
  author,
2712
2856
  modified,
2713
2857
  };
@@ -2723,23 +2867,18 @@ class PdfiumEngine {
2723
2867
  * @private
2724
2868
  */
2725
2869
  readPdfPolygonAnno(page, pagePtr, annotationPtr, index) {
2726
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2727
2870
  const pageRect = this.readPageAnnoRect(annotationPtr);
2728
2871
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2729
2872
  const author = this.getAnnotString(annotationPtr, 'T');
2730
2873
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2731
- const modified = this.toIsoDate(modifiedRaw);
2732
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2874
+ const modified = pdfDateToDate(modifiedRaw);
2733
2875
  const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2734
2876
  return {
2735
- status: PdfAnnotationObjectStatus.Committed,
2736
2877
  pageIndex: page.index,
2737
2878
  id: index,
2738
2879
  type: PdfAnnotationSubtype.POLYGON,
2739
2880
  rect,
2740
- popup,
2741
2881
  vertices,
2742
- appearances,
2743
2882
  author,
2744
2883
  modified,
2745
2884
  };
@@ -2755,23 +2894,18 @@ class PdfiumEngine {
2755
2894
  * @private
2756
2895
  */
2757
2896
  readPdfPolylineAnno(page, pagePtr, annotationPtr, index) {
2758
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2759
2897
  const pageRect = this.readPageAnnoRect(annotationPtr);
2760
2898
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2761
2899
  const author = this.getAnnotString(annotationPtr, 'T');
2762
2900
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2763
- const modified = this.toIsoDate(modifiedRaw);
2764
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2901
+ const modified = pdfDateToDate(modifiedRaw);
2765
2902
  const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2766
2903
  return {
2767
- status: PdfAnnotationObjectStatus.Committed,
2768
2904
  pageIndex: page.index,
2769
2905
  id: index,
2770
2906
  type: PdfAnnotationSubtype.POLYLINE,
2771
2907
  rect,
2772
- popup,
2773
2908
  vertices,
2774
- appearances,
2775
2909
  author,
2776
2910
  modified,
2777
2911
  };
@@ -2787,13 +2921,11 @@ class PdfiumEngine {
2787
2921
  * @private
2788
2922
  */
2789
2923
  readPdfLineAnno(page, pagePtr, annotationPtr, index) {
2790
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2791
2924
  const pageRect = this.readPageAnnoRect(annotationPtr);
2792
2925
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2793
2926
  const author = this.getAnnotString(annotationPtr, 'T');
2794
2927
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2795
- const modified = this.toIsoDate(modifiedRaw);
2796
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2928
+ const modified = pdfDateToDate(modifiedRaw);
2797
2929
  const startPointPtr = this.malloc(8);
2798
2930
  const endPointPtr = this.malloc(8);
2799
2931
  this.pdfiumModule.FPDFAnnot_GetLine(annotationPtr, startPointPtr, endPointPtr);
@@ -2812,15 +2944,12 @@ class PdfiumEngine {
2812
2944
  this.free(startPointPtr);
2813
2945
  this.free(endPointPtr);
2814
2946
  return {
2815
- status: PdfAnnotationObjectStatus.Committed,
2816
2947
  pageIndex: page.index,
2817
2948
  id: index,
2818
2949
  type: PdfAnnotationSubtype.LINE,
2819
2950
  rect,
2820
- popup,
2821
2951
  startPoint,
2822
2952
  endPoint,
2823
- appearances,
2824
2953
  author,
2825
2954
  modified,
2826
2955
  };
@@ -2836,25 +2965,22 @@ class PdfiumEngine {
2836
2965
  * @private
2837
2966
  */
2838
2967
  readPdfHighlightAnno(page, pagePtr, annotationPtr, index) {
2839
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2840
2968
  const pageRect = this.readPageAnnoRect(annotationPtr);
2841
2969
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2842
- const quads = this.readAnnotationQuads(page, annotationPtr);
2843
- const color = this.resolveAnnotationColor(annotationPtr);
2844
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2970
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
2971
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2845
2972
  const author = this.getAnnotString(annotationPtr, 'T');
2846
2973
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2847
- const modified = this.toIsoDate(modifiedRaw);
2974
+ const modified = pdfDateToDate(modifiedRaw);
2975
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2848
2976
  return {
2849
- status: PdfAnnotationObjectStatus.Committed,
2850
2977
  pageIndex: page.index,
2851
2978
  id: index,
2852
2979
  type: PdfAnnotationSubtype.HIGHLIGHT,
2853
2980
  rect,
2854
- popup,
2855
- appearances,
2856
- segmentRects: quads.map(quadToRect),
2857
- color,
2981
+ contents,
2982
+ segmentRects,
2983
+ ...webAlphaColor,
2858
2984
  author,
2859
2985
  modified,
2860
2986
  };
@@ -2870,21 +2996,22 @@ class PdfiumEngine {
2870
2996
  * @private
2871
2997
  */
2872
2998
  readPdfUnderlineAnno(page, pagePtr, annotationPtr, index) {
2873
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2874
2999
  const pageRect = this.readPageAnnoRect(annotationPtr);
2875
3000
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2876
3001
  const author = this.getAnnotString(annotationPtr, 'T');
2877
3002
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2878
- const modified = this.toIsoDate(modifiedRaw);
2879
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3003
+ const modified = pdfDateToDate(modifiedRaw);
3004
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3005
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3006
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2880
3007
  return {
2881
- status: PdfAnnotationObjectStatus.Committed,
2882
3008
  pageIndex: page.index,
2883
3009
  id: index,
2884
3010
  type: PdfAnnotationSubtype.UNDERLINE,
2885
3011
  rect,
2886
- popup,
2887
- appearances,
3012
+ contents,
3013
+ segmentRects,
3014
+ ...webAlphaColor,
2888
3015
  author,
2889
3016
  modified,
2890
3017
  };
@@ -2900,21 +3027,22 @@ class PdfiumEngine {
2900
3027
  * @private
2901
3028
  */
2902
3029
  readPdfStrikeOutAnno(page, pagePtr, annotationPtr, index) {
2903
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2904
3030
  const pageRect = this.readPageAnnoRect(annotationPtr);
2905
3031
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2906
3032
  const author = this.getAnnotString(annotationPtr, 'T');
2907
3033
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2908
- const modified = this.toIsoDate(modifiedRaw);
2909
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3034
+ const modified = pdfDateToDate(modifiedRaw);
3035
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3036
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3037
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2910
3038
  return {
2911
- status: PdfAnnotationObjectStatus.Committed,
2912
3039
  pageIndex: page.index,
2913
3040
  id: index,
2914
3041
  type: PdfAnnotationSubtype.STRIKEOUT,
2915
3042
  rect,
2916
- popup,
2917
- appearances,
3043
+ contents,
3044
+ segmentRects,
3045
+ ...webAlphaColor,
2918
3046
  author,
2919
3047
  modified,
2920
3048
  };
@@ -2930,21 +3058,22 @@ class PdfiumEngine {
2930
3058
  * @private
2931
3059
  */
2932
3060
  readPdfSquigglyAnno(page, pagePtr, annotationPtr, index) {
2933
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2934
3061
  const pageRect = this.readPageAnnoRect(annotationPtr);
2935
3062
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2936
3063
  const author = this.getAnnotString(annotationPtr, 'T');
2937
3064
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2938
- const modified = this.toIsoDate(modifiedRaw);
2939
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3065
+ const modified = pdfDateToDate(modifiedRaw);
3066
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3067
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3068
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2940
3069
  return {
2941
- status: PdfAnnotationObjectStatus.Committed,
2942
3070
  pageIndex: page.index,
2943
3071
  id: index,
2944
3072
  type: PdfAnnotationSubtype.SQUIGGLY,
2945
3073
  rect,
2946
- popup,
2947
- appearances,
3074
+ contents,
3075
+ segmentRects,
3076
+ ...webAlphaColor,
2948
3077
  author,
2949
3078
  modified,
2950
3079
  };
@@ -2960,21 +3089,16 @@ class PdfiumEngine {
2960
3089
  * @private
2961
3090
  */
2962
3091
  readPdfCaretAnno(page, pagePtr, annotationPtr, index) {
2963
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2964
3092
  const pageRect = this.readPageAnnoRect(annotationPtr);
2965
3093
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2966
3094
  const author = this.getAnnotString(annotationPtr, 'T');
2967
3095
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2968
- const modified = this.toIsoDate(modifiedRaw);
2969
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3096
+ const modified = pdfDateToDate(modifiedRaw);
2970
3097
  return {
2971
- status: PdfAnnotationObjectStatus.Committed,
2972
3098
  pageIndex: page.index,
2973
3099
  id: index,
2974
3100
  type: PdfAnnotationSubtype.CARET,
2975
3101
  rect,
2976
- popup,
2977
- appearances,
2978
3102
  author,
2979
3103
  modified,
2980
3104
  };
@@ -2991,13 +3115,11 @@ class PdfiumEngine {
2991
3115
  * @private
2992
3116
  */
2993
3117
  readPdfStampAnno(docPtr, page, pagePtr, annotationPtr, index) {
2994
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2995
3118
  const pageRect = this.readPageAnnoRect(annotationPtr);
2996
3119
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2997
3120
  const author = this.getAnnotString(annotationPtr, 'T');
2998
3121
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2999
- const modified = this.toIsoDate(modifiedRaw);
3000
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3122
+ const modified = pdfDateToDate(modifiedRaw);
3001
3123
  const contents = [];
3002
3124
  const objectCount = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotationPtr);
3003
3125
  for (let i = 0; i < objectCount; i++) {
@@ -3008,14 +3130,11 @@ class PdfiumEngine {
3008
3130
  }
3009
3131
  }
3010
3132
  return {
3011
- status: PdfAnnotationObjectStatus.Committed,
3012
3133
  pageIndex: page.index,
3013
3134
  id: index,
3014
3135
  type: PdfAnnotationSubtype.STAMP,
3015
3136
  rect,
3016
- popup,
3017
3137
  contents,
3018
- appearances,
3019
3138
  author,
3020
3139
  modified,
3021
3140
  };
@@ -3184,6 +3303,39 @@ class PdfiumEngine {
3184
3303
  this.free(matrixPtr);
3185
3304
  return { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
3186
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
+ }
3187
3339
  /**
3188
3340
  * Read circle annotation
3189
3341
  * @param page - pdf page infor
@@ -3195,23 +3347,51 @@ class PdfiumEngine {
3195
3347
  * @private
3196
3348
  */
3197
3349
  readPdfCircleAnno(page, pagePtr, annotationPtr, index) {
3198
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3350
+ const flags = this.getAnnotationFlags(annotationPtr);
3199
3351
  const pageRect = this.readPageAnnoRect(annotationPtr);
3200
3352
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3201
3353
  const author = this.getAnnotString(annotationPtr, 'T');
3202
3354
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3203
- const modified = this.toIsoDate(modifiedRaw);
3204
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
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
+ }
3205
3379
  return {
3206
- status: PdfAnnotationObjectStatus.Committed,
3207
3380
  pageIndex: page.index,
3208
3381
  id: index,
3209
3382
  type: PdfAnnotationSubtype.CIRCLE,
3383
+ flags,
3384
+ color,
3385
+ opacity,
3386
+ strokeWidth,
3387
+ strokeColor,
3388
+ strokeStyle,
3210
3389
  rect,
3211
- popup,
3212
- appearances,
3213
3390
  author,
3214
3391
  modified,
3392
+ ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3393
+ ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3394
+ ...(strokeDashArray !== undefined && { strokeDashArray }),
3215
3395
  };
3216
3396
  }
3217
3397
  /**
@@ -3225,23 +3405,51 @@ class PdfiumEngine {
3225
3405
  * @private
3226
3406
  */
3227
3407
  readPdfSquareAnno(page, pagePtr, annotationPtr, index) {
3228
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3408
+ const flags = this.getAnnotationFlags(annotationPtr);
3229
3409
  const pageRect = this.readPageAnnoRect(annotationPtr);
3230
3410
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3231
3411
  const author = this.getAnnotString(annotationPtr, 'T');
3232
3412
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3233
- const modified = this.toIsoDate(modifiedRaw);
3234
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
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
+ }
3235
3437
  return {
3236
- status: PdfAnnotationObjectStatus.Committed,
3237
3438
  pageIndex: page.index,
3238
3439
  id: index,
3239
3440
  type: PdfAnnotationSubtype.SQUARE,
3441
+ flags,
3442
+ color,
3443
+ opacity,
3444
+ strokeColor,
3445
+ strokeWidth,
3446
+ strokeStyle,
3240
3447
  rect,
3241
- popup,
3242
- appearances,
3243
3448
  author,
3244
3449
  modified,
3450
+ ...(cloudyBorderIntensity !== undefined && { cloudyBorderIntensity }),
3451
+ ...(cloudyBorderInset !== undefined && { cloudyBorderInset }),
3452
+ ...(strokeDashArray !== undefined && { strokeDashArray }),
3245
3453
  };
3246
3454
  }
3247
3455
  /**
@@ -3256,21 +3464,16 @@ class PdfiumEngine {
3256
3464
  * @private
3257
3465
  */
3258
3466
  readPdfAnno(page, pagePtr, type, annotationPtr, index) {
3259
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3260
3467
  const pageRect = this.readPageAnnoRect(annotationPtr);
3261
3468
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3262
3469
  const author = this.getAnnotString(annotationPtr, 'T');
3263
3470
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3264
- const modified = this.toIsoDate(modifiedRaw);
3265
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3471
+ const modified = pdfDateToDate(modifiedRaw);
3266
3472
  return {
3267
- status: PdfAnnotationObjectStatus.Committed,
3268
3473
  pageIndex: page.index,
3269
3474
  id: index,
3270
3475
  type,
3271
3476
  rect,
3272
- popup,
3273
- appearances,
3274
3477
  author,
3275
3478
  modified,
3276
3479
  };
@@ -3292,25 +3495,6 @@ class PdfiumEngine {
3292
3495
  const idx = this.pdfiumModule.FPDFPage_GetAnnotIndex(pagePtr, parentPtr);
3293
3496
  return idx >= 0 ? idx : undefined;
3294
3497
  }
3295
- /**
3296
- * Parse a PDF date string **D:YYYYMMDDHHmmSSOHH'mm'** to ISO-8601.
3297
- *
3298
- * Returns `undefined` if the input is malformed.
3299
- *
3300
- * @private
3301
- */
3302
- toIsoDate(pdfDate) {
3303
- if (!pdfDate?.startsWith('D:'))
3304
- return;
3305
- // Minimal parse – ignore timezone for brevity
3306
- const y = pdfDate.substring(2, 6);
3307
- const m = pdfDate.substring(6, 8) || '01';
3308
- const d = pdfDate.substring(8, 10) || '01';
3309
- const H = pdfDate.substring(10, 12) || '00';
3310
- const M = pdfDate.substring(12, 14) || '00';
3311
- const S = pdfDate.substring(14, 16) || '00';
3312
- return `${y}-${m}-${d}T${H}:${M}:${S}`;
3313
- }
3314
3498
  /**
3315
3499
  * Fetch a string value (`/T`, `/M`, `/State`, …) from an annotation.
3316
3500
  *
@@ -3330,41 +3514,19 @@ class PdfiumEngine {
3330
3514
  return value || undefined;
3331
3515
  }
3332
3516
  /**
3333
- * Read linked popup of pdf annotation
3334
- * @param page - pdf page infor
3335
- * @param pagePtr - pointer to pdf page object
3336
- * @param annotationPtr - pointer to pdf annotation
3337
- * @param index - index of annotation in the pdf page
3338
- * @returns pdf popup linked to annotation
3517
+ * Set a string value (`/T`, `/M`, `/State`, …) to an annotation.
3518
+ *
3519
+ * @returns `true` if the operation was successful
3339
3520
  *
3340
3521
  * @private
3341
3522
  */
3342
- readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index) {
3343
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3344
- const popupAnnotationPtr = this.pdfiumModule.FPDFAnnot_GetLinkedAnnot(annotationPtr, 'Popup');
3345
- if (!popupAnnotationPtr) {
3346
- return;
3347
- }
3348
- const pageRect = this.readPageAnnoRect(popupAnnotationPtr);
3349
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3350
- const author = this.getAnnotString(annotationPtr, 'T');
3351
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3352
- const modified = this.toIsoDate(modifiedRaw);
3353
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3354
- const open = this.getAnnotString(annotationPtr, 'Open') || 'false';
3355
- this.pdfiumModule.FPDFPage_CloseAnnot(popupAnnotationPtr);
3356
- return {
3357
- status: PdfAnnotationObjectStatus.Committed,
3358
- pageIndex: page.index,
3359
- id: index,
3360
- type: PdfAnnotationSubtype.POPUP,
3361
- rect,
3362
- contents,
3363
- open: open === 'true',
3364
- appearances,
3365
- author,
3366
- modified,
3367
- };
3523
+ setAnnotString(annotationPtr, key, value) {
3524
+ const bytes = 2 * (value.length + 1);
3525
+ const ptr = this.malloc(bytes);
3526
+ this.pdfiumModule.pdfium.stringToUTF16(value, ptr, bytes);
3527
+ const ok = this.pdfiumModule.FPDFAnnot_SetStringValue(annotationPtr, key, ptr);
3528
+ this.free(ptr);
3529
+ return ok;
3368
3530
  }
3369
3531
  /**
3370
3532
  * Read vertices of pdf annotation
@@ -3473,6 +3635,81 @@ class PdfiumEngine {
3473
3635
  options,
3474
3636
  };
3475
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
+ }
3476
3713
  /**
3477
3714
  * render rectangle of pdf page to image
3478
3715
  * @param docPtr - pointer to pdf document object
@@ -3805,6 +4042,64 @@ class PdfiumEngine {
3805
4042
  this.free(bufferPtr);
3806
4043
  return ap;
3807
4044
  }
4045
+ /**
4046
+ * Change the visible colour (and opacity) of an existing annotation.
4047
+ *
4048
+ * For markup annotations (highlight / underline / strikeout / squiggly) we
4049
+ * first clear the AP dictionary entry, otherwise the stored appearance stream
4050
+ * will override the new tint. For all other sub-types we keep the existing
4051
+ * AP so custom artwork isn't lost.
4052
+ *
4053
+ * @param doc logical document object
4054
+ * @param page logical page object
4055
+ * @param annotation the annotation we want to recolour
4056
+ * @param colour RGBA tuple (0-255 per channel)
4057
+ * @param which 0 = stroke/fill colour (PDFium's "colourType" param)
4058
+ *
4059
+ * @returns `true` when the operation succeeded
4060
+ */
4061
+ updateAnnotationColor(doc, page, annotation, color, which = 0) {
4062
+ this.logger.debug(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', doc, page, annotation, color, which);
4063
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', 'Begin', doc.id);
4064
+ const task = PdfTaskHelper.create();
4065
+ try {
4066
+ /* 1 ── sanity & native handles ────────────────────────────────────────── */
4067
+ const ctx = this.cache.getContext(doc.id);
4068
+ if (!ctx) {
4069
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', 'End', doc.id);
4070
+ this.logger.warn(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor: doc closed');
4071
+ task.resolve(false);
4072
+ return task;
4073
+ }
4074
+ const pageCtx = ctx.acquirePage(page.index);
4075
+ const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
4076
+ if (!annotPtr) {
4077
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', 'End', doc.id);
4078
+ this.logger.warn(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor: annot not found');
4079
+ pageCtx.release();
4080
+ task.resolve(false);
4081
+ return task;
4082
+ }
4083
+ const ok = this.setAnnotationColor(annotPtr, color, which);
4084
+ /* 4 ── regenerate appearance & clean-up ───────────────────────────────── */
4085
+ if (ok) {
4086
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
4087
+ }
4088
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
4089
+ pageCtx.release();
4090
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', 'End', doc.id);
4091
+ task.resolve(!!ok);
4092
+ }
4093
+ catch (error) {
4094
+ this.logger.perf(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor', 'End', doc.id);
4095
+ this.logger.error(LOG_SOURCE$1, LOG_CATEGORY$1, 'setAnnotationColor: error', error);
4096
+ task.reject({
4097
+ code: PdfErrorCode.Unknown,
4098
+ message: `Failed to set annotation color: ${error instanceof Error ? error.message : String(error)}`,
4099
+ });
4100
+ }
4101
+ return task;
4102
+ }
3808
4103
  /**
3809
4104
  * Set the rect of specified annotation
3810
4105
  * @param page - page info that the annotation is belonged to
@@ -4140,7 +4435,7 @@ class EngineRunner {
4140
4435
  type: 'reject',
4141
4436
  reason: {
4142
4437
  code: PdfErrorCode.NotSupport,
4143
- message: 'engine method has not supported yet',
4438
+ message: `engine method ${name} is not supported yet`,
4144
4439
  },
4145
4440
  };
4146
4441
  const response = {
@@ -4195,6 +4490,9 @@ class EngineRunner {
4195
4490
  case 'renderPageRect':
4196
4491
  task = this.engine[name](...args);
4197
4492
  break;
4493
+ case 'renderAnnotation':
4494
+ task = this.engine[name](...args);
4495
+ break;
4198
4496
  case 'renderThumbnail':
4199
4497
  task = this.engine[name](...args);
4200
4498
  break;
@@ -4207,12 +4505,15 @@ class EngineRunner {
4207
4505
  case 'createPageAnnotation':
4208
4506
  task = this.engine[name](...args);
4209
4507
  break;
4210
- case 'transformPageAnnotation':
4508
+ case 'updatePageAnnotation':
4211
4509
  task = this.engine[name](...args);
4212
4510
  break;
4213
4511
  case 'removePageAnnotation':
4214
4512
  task = this.engine[name](...args);
4215
4513
  break;
4514
+ case 'updateAnnotationColor':
4515
+ task = this.engine[name](...args);
4516
+ break;
4216
4517
  case 'getPageTextRects':
4217
4518
  task = this.engine[name](...args);
4218
4519
  break;