@embedpdf/engines 1.0.5 → 1.0.7

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.
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var models = require('@embedpdf/models');
4
3
  var pdfium = require('@embedpdf/pdfium');
4
+ var models = require('@embedpdf/models');
5
5
 
6
6
  /**
7
7
  * Read string from WASM heap
@@ -917,6 +917,12 @@ class PdfiumEngine {
917
917
  case models.PdfAnnotationSubtype.STAMP:
918
918
  isSucceed = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotationPtr, annotation.rect, annotation.contents);
919
919
  break;
920
+ case models.PdfAnnotationSubtype.UNDERLINE:
921
+ case models.PdfAnnotationSubtype.STRIKEOUT:
922
+ case models.PdfAnnotationSubtype.SQUIGGLY:
923
+ case models.PdfAnnotationSubtype.HIGHLIGHT:
924
+ isSucceed = this.addTextMarkupContent(page, annotationPtr, annotation);
925
+ break;
920
926
  }
921
927
  if (!isSucceed) {
922
928
  this.pdfiumModule.FPDFPage_RemoveAnnot(pageCtx.pagePtr, annotationPtr);
@@ -928,10 +934,101 @@ class PdfiumEngine {
928
934
  });
929
935
  }
930
936
  this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
937
+ const annotId = this.pdfiumModule.FPDFPage_GetAnnotIndex(pageCtx.pagePtr, annotationPtr);
931
938
  this.pdfiumModule.FPDFPage_CloseAnnot(annotationPtr);
932
939
  pageCtx.release();
933
940
  this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `CreatePageAnnotation`, 'End', `${doc.id}-${page.index}`);
934
- return models.PdfTaskHelper.resolve(true);
941
+ return annotId >= 0
942
+ ? models.PdfTaskHelper.resolve(annotId)
943
+ : models.PdfTaskHelper.reject({
944
+ code: models.PdfErrorCode.CantCreateAnnot,
945
+ message: 'annotation created but index could not be determined',
946
+ });
947
+ }
948
+ /**
949
+ * Update an existing page annotation in-place
950
+ *
951
+ * • Locates the annot by page-local index (`annotation.id`)
952
+ * • Re-writes its /Rect and type-specific payload
953
+ * • Calls FPDFPage_GenerateContent so the new appearance is rendered
954
+ *
955
+ * @returns PdfTask<boolean> – true on success
956
+ */
957
+ updatePageAnnotation(doc, page, annotation) {
958
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'updatePageAnnotation', doc, page, annotation);
959
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'Begin', `${doc.id}-${page.index}`);
960
+ const ctx = this.cache.getContext(doc.id);
961
+ if (!ctx) {
962
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
963
+ return models.PdfTaskHelper.reject({
964
+ code: models.PdfErrorCode.DocNotOpen,
965
+ message: 'document does not open',
966
+ });
967
+ }
968
+ const pageCtx = ctx.acquirePage(page.index);
969
+ const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
970
+ if (!annotPtr) {
971
+ pageCtx.release();
972
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
973
+ return models.PdfTaskHelper.reject({ code: models.PdfErrorCode.NotFound, message: 'annotation not found' });
974
+ }
975
+ /* 1 ── (re)set bounding-box ────────────────────────────────────────────── */
976
+ if (!this.setPageAnnoRect(page, pageCtx.pagePtr, annotPtr, annotation.rect)) {
977
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
978
+ pageCtx.release();
979
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
980
+ return models.PdfTaskHelper.reject({
981
+ code: models.PdfErrorCode.CantSetAnnotRect,
982
+ message: 'failed to move annotation',
983
+ });
984
+ }
985
+ /* 2 ── wipe previous payload and rebuild fresh one ─────────────────────── */
986
+ let ok = false;
987
+ switch (annotation.type) {
988
+ /* ── Ink ─────────────────────────────────────────────────────────────── */
989
+ case models.PdfAnnotationSubtype.INK: {
990
+ /* clear every existing stroke first */
991
+ if (!this.pdfiumModule.FPDFAnnot_RemoveInkList(annotPtr))
992
+ break;
993
+ ok = this.addInkStroke(page, pageCtx.pagePtr, annotPtr, annotation.inkList);
994
+ break;
995
+ }
996
+ /* ── Stamp ───────────────────────────────────────────────────────────── */
997
+ case models.PdfAnnotationSubtype.STAMP: {
998
+ /* drop every page-object inside the annot */
999
+ for (let i = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotPtr) - 1; i >= 0; i--) {
1000
+ this.pdfiumModule.FPDFAnnot_RemoveObject(annotPtr, i);
1001
+ }
1002
+ ok = this.addStampContent(ctx.docPtr, page, pageCtx.pagePtr, annotPtr, annotation.rect, annotation.contents);
1003
+ break;
1004
+ }
1005
+ /* ── Text-markup family ──────────────────────────────────────────────── */
1006
+ case models.PdfAnnotationSubtype.HIGHLIGHT:
1007
+ case models.PdfAnnotationSubtype.UNDERLINE:
1008
+ case models.PdfAnnotationSubtype.STRIKEOUT:
1009
+ case models.PdfAnnotationSubtype.SQUIGGLY: {
1010
+ /* replace quad-points / colour / strings in one go */
1011
+ ok = this.addTextMarkupContent(page, annotPtr, annotation, true);
1012
+ break;
1013
+ }
1014
+ /* ── Unsupported edits – fall through to error ───────────────────────── */
1015
+ default:
1016
+ ok = false;
1017
+ }
1018
+ /* 3 ── regenerate appearance if payload was changed ───────────────────── */
1019
+ if (ok) {
1020
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
1021
+ }
1022
+ /* 4 ── tidy-up native handles ──────────────────────────────────────────── */
1023
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
1024
+ pageCtx.release();
1025
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'UpdatePageAnnotation', 'End', `${doc.id}-${page.index}`);
1026
+ return ok
1027
+ ? models.PdfTaskHelper.resolve(true)
1028
+ : models.PdfTaskHelper.reject({
1029
+ code: models.PdfErrorCode.CantSetAnnotContent,
1030
+ message: 'failed to update annotation',
1031
+ });
935
1032
  }
936
1033
  /**
937
1034
  * {@inheritDoc @embedpdf/models!PdfEngine.transformPageAnnotation}
@@ -1364,6 +1461,62 @@ class PdfiumEngine {
1364
1461
  this.logger.perf(LOG_SOURCE, LOG_CATEGORY, `ExtractText`, 'End', doc.id);
1365
1462
  return models.PdfTaskHelper.resolve(text);
1366
1463
  }
1464
+ /**
1465
+ * {@inheritDoc @embedpdf/models!PdfEngine.getTextSlices}
1466
+ *
1467
+ * @public
1468
+ */
1469
+ getTextSlices(doc, slices) {
1470
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'getTextSlices', doc, slices);
1471
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'Begin', doc.id);
1472
+ /* ⚠︎ 1 — trivial case */
1473
+ if (slices.length === 0) {
1474
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1475
+ return models.PdfTaskHelper.resolve([]);
1476
+ }
1477
+ /* ⚠︎ 2 — document must be open */
1478
+ const ctx = this.cache.getContext(doc.id);
1479
+ if (!ctx) {
1480
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1481
+ return models.PdfTaskHelper.reject({
1482
+ code: models.PdfErrorCode.DocNotOpen,
1483
+ message: 'document does not open',
1484
+ });
1485
+ }
1486
+ try {
1487
+ /* keep caller order */
1488
+ const out = new Array(slices.length);
1489
+ /* group → open each page once */
1490
+ const byPage = new Map();
1491
+ slices.forEach((s, i) => {
1492
+ (byPage.get(s.pageIndex) ?? byPage.set(s.pageIndex, []).get(s.pageIndex)).push({
1493
+ slice: s,
1494
+ pos: i,
1495
+ });
1496
+ });
1497
+ for (const [pageIdx, list] of byPage) {
1498
+ const pageCtx = ctx.acquirePage(pageIdx);
1499
+ const textPagePtr = pageCtx.getTextPage();
1500
+ for (const { slice, pos } of list) {
1501
+ const bufPtr = this.malloc(2 * (slice.charCount + 1)); // UTF-16 + NIL
1502
+ this.pdfiumModule.FPDFText_GetText(textPagePtr, slice.charIndex, slice.charCount, bufPtr);
1503
+ out[pos] = models.stripPdfUnwantedMarkers(this.pdfiumModule.pdfium.UTF16ToString(bufPtr));
1504
+ this.free(bufPtr);
1505
+ }
1506
+ pageCtx.release();
1507
+ }
1508
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1509
+ return models.PdfTaskHelper.resolve(out);
1510
+ }
1511
+ catch (e) {
1512
+ this.logger.error(LOG_SOURCE, LOG_CATEGORY, 'getTextSlices error', e);
1513
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'GetTextSlices', 'End', doc.id);
1514
+ return models.PdfTaskHelper.reject({
1515
+ code: models.PdfErrorCode.Unknown,
1516
+ message: String(e),
1517
+ });
1518
+ }
1519
+ }
1367
1520
  /**
1368
1521
  * {@inheritDoc @embedpdf/models!PdfEngine.merge}
1369
1522
  *
@@ -1594,6 +1747,36 @@ class PdfiumEngine {
1594
1747
  }
1595
1748
  return true;
1596
1749
  }
1750
+ /**
1751
+ * Add highlight content to annotation
1752
+ * @param page - page info
1753
+ * @param annotationPtr - pointer to highlight annotation
1754
+ * @param annotation - highlight annotation
1755
+ * @returns whether highlight content is added to annotation
1756
+ *
1757
+ * @private
1758
+ */
1759
+ addTextMarkupContent(page, annotationPtr, annotation, shouldClearAP = false) {
1760
+ if (!this.syncQuadPointsAnno(page, annotationPtr, annotation.segmentRects)) {
1761
+ return false;
1762
+ }
1763
+ if (!this.setAnnotString(annotationPtr, 'Contents', annotation.contents ?? '')) {
1764
+ return false;
1765
+ }
1766
+ if (!this.setAnnotString(annotationPtr, 'T', annotation.author || '')) {
1767
+ return false;
1768
+ }
1769
+ if (!this.setAnnotString(annotationPtr, 'M', models.dateToPdfDate(annotation.modified))) {
1770
+ return false;
1771
+ }
1772
+ if (!this.setAnnotationColor(annotationPtr, {
1773
+ color: annotation.color ?? '#FFFF00',
1774
+ opacity: annotation.opacity ?? 1,
1775
+ }, shouldClearAP, 0)) {
1776
+ return false;
1777
+ }
1778
+ return true;
1779
+ }
1597
1780
  /**
1598
1781
  * Add contents to stamp annotation
1599
1782
  * @param docPtr - pointer to pdf document object
@@ -1893,14 +2076,12 @@ class PdfiumEngine {
1893
2076
  const runs = [];
1894
2077
  let current = null;
1895
2078
  let curObjPtr = null;
2079
+ let bounds = null;
1896
2080
  /** ── main loop ──────────────────────────────────────────── */
1897
2081
  for (let i = 0; i < glyphs.length; i++) {
1898
2082
  const g = glyphs[i];
1899
2083
  /* 1 — find the CPDF_TextObject this glyph belongs to */
1900
2084
  const objPtr = this.pdfiumModule.FPDFText_GetTextObject(textPagePtr, i);
1901
- if (g.isEmpty) {
1902
- continue;
1903
- }
1904
2085
  /* 2 — start a new run when the text object changes */
1905
2086
  if (objPtr !== curObjPtr) {
1906
2087
  curObjPtr = objPtr;
@@ -1914,6 +2095,12 @@ class PdfiumEngine {
1914
2095
  charStart: i,
1915
2096
  glyphs: [],
1916
2097
  };
2098
+ bounds = {
2099
+ minX: g.origin.x,
2100
+ minY: g.origin.y,
2101
+ maxX: g.origin.x + g.size.width,
2102
+ maxY: g.origin.y + g.size.height,
2103
+ };
1917
2104
  runs.push(current);
1918
2105
  }
1919
2106
  /* 3 — append the slim glyph record */
@@ -1922,16 +2109,24 @@ class PdfiumEngine {
1922
2109
  y: g.origin.y,
1923
2110
  width: g.size.width,
1924
2111
  height: g.size.height,
1925
- flags: g.isSpace ? 1 : 0,
2112
+ flags: g.isEmpty ? 2 : g.isSpace ? 1 : 0,
1926
2113
  });
1927
2114
  /* 4 — expand the run's bounding rect */
2115
+ if (g.isEmpty) {
2116
+ continue;
2117
+ }
1928
2118
  const right = g.origin.x + g.size.width;
1929
2119
  const bottom = g.origin.y + g.size.height;
1930
- current.rect.width =
1931
- Math.max(current.rect.x + current.rect.width, right) - current.rect.x;
1932
- current.rect.y = Math.min(current.rect.y, g.origin.y);
1933
- current.rect.height =
1934
- Math.max(current.rect.y + current.rect.height, bottom) - current.rect.y;
2120
+ // Update bounds
2121
+ bounds.minX = Math.min(bounds.minX, g.origin.x);
2122
+ bounds.minY = Math.min(bounds.minY, g.origin.y);
2123
+ bounds.maxX = Math.max(bounds.maxX, right);
2124
+ bounds.maxY = Math.max(bounds.maxY, bottom);
2125
+ // Calculate final rect from bounds
2126
+ current.rect.x = bounds.minX;
2127
+ current.rect.y = bounds.minY;
2128
+ current.rect.width = bounds.maxX - bounds.minX;
2129
+ current.rect.height = bounds.maxY - bounds.minY;
1935
2130
  }
1936
2131
  return runs;
1937
2132
  }
@@ -2204,8 +2399,6 @@ class PdfiumEngine {
2204
2399
  annotation = this.readPdfCaretAnno(page, pageCtx.pagePtr, annotationPtr, index);
2205
2400
  }
2206
2401
  break;
2207
- case models.PdfAnnotationSubtype.POPUP:
2208
- break;
2209
2402
  default:
2210
2403
  {
2211
2404
  annotation = this.readPdfAnno(page, pageCtx.pagePtr, subType, annotationPtr, index);
@@ -2352,15 +2545,35 @@ class PdfiumEngine {
2352
2545
  *
2353
2546
  * @param annotationPtr - pointer to an `FPDF_ANNOTATION`
2354
2547
  * @param fallback - colour to use when the PDF stores no tint at all
2355
- * @returns Guaranteed RGBA tuple (never `undefined`)
2548
+ * @returns WebAlphaColor with hex color and opacity (0-1)
2356
2549
  *
2357
2550
  * @private
2358
2551
  */
2359
2552
  resolveAnnotationColor(annotationPtr, fallback = { red: 255, green: 245, blue: 155, alpha: 255 }) {
2360
- return (this.readAnnotationColor(annotationPtr) ?? // 1 – /C entry
2553
+ const pdfColor = this.readAnnotationColor(annotationPtr) ?? // 1 – /C entry
2361
2554
  this.colorFromAppearance(annotationPtr) ?? // 2 – AP stream walk
2362
- fallback // 3 – default
2363
- );
2555
+ fallback; // 3 – default
2556
+ return models.pdfAlphaColorToWebAlphaColor(pdfColor);
2557
+ }
2558
+ /**
2559
+ * Set the fill/stroke colour for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2560
+ *
2561
+ * @param annotationPtr - pointer to the annotation whose colour is being set
2562
+ * @param webAlphaColor - WebAlphaColor with hex color and opacity (0-1)
2563
+ * @param shouldClearAP - whether to clear the /AP entry
2564
+ * @param which - which colour to set (0 = fill, 1 = stroke)
2565
+ * @returns `true` if the operation was successful
2566
+ *
2567
+ * @private
2568
+ */
2569
+ setAnnotationColor(annotationPtr, webAlphaColor, shouldClearAP = false, which = 0) {
2570
+ const pdfAlphaColor = models.webAlphaColorToPdfAlphaColor(webAlphaColor);
2571
+ if (shouldClearAP) {
2572
+ // NULL wide-string → remove the /AP entry
2573
+ this.pdfiumModule.FPDFAnnot_SetAP(annotationPtr, models.AppearanceMode.Normal,
2574
+ /* FPDF_WIDESTRING = */ 0);
2575
+ }
2576
+ return this.pdfiumModule.FPDFAnnot_SetColor(annotationPtr, which, pdfAlphaColor.red & 0xff, pdfAlphaColor.green & 0xff, pdfAlphaColor.blue & 0xff, (pdfAlphaColor.alpha ?? 255) & 0xff);
2364
2577
  }
2365
2578
  /**
2366
2579
  * Read `/QuadPoints` from any annotation and convert each quadrilateral to
@@ -2373,11 +2586,11 @@ class PdfiumEngine {
2373
2586
  *
2374
2587
  * @param page - logical page info object (`PdfPageObject`)
2375
2588
  * @param annotationPtr - pointer to the annotation whose quads are needed
2376
- * @returns Array of `Quad` objects (`[]` if the annotation has no quads)
2589
+ * @returns Array of `Rect` objects (`[]` if the annotation has no quads)
2377
2590
  *
2378
2591
  * @private
2379
2592
  */
2380
- readAnnotationQuads(page, annotationPtr) {
2593
+ getQuadPointsAnno(page, annotationPtr) {
2381
2594
  const quadCount = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotationPtr);
2382
2595
  if (quadCount === 0)
2383
2596
  return [];
@@ -2404,7 +2617,60 @@ class PdfiumEngine {
2404
2617
  }
2405
2618
  this.free(quadPtr);
2406
2619
  }
2407
- return quads;
2620
+ return quads.map(models.quadToRect);
2621
+ }
2622
+ /**
2623
+ * Set the quadrilaterals for a **Highlight / Underline / StrikeOut / Squiggly** markup annotation.
2624
+ *
2625
+ * @param page - logical page info object (`PdfPageObject`)
2626
+ * @param annotationPtr - pointer to the annotation whose quads are needed
2627
+ * @param rects - array of `Rect` objects (`[]` if the annotation has no quads)
2628
+ * @returns `true` if the operation was successful
2629
+ *
2630
+ * @private
2631
+ */
2632
+ syncQuadPointsAnno(page, annotPtr, rects) {
2633
+ const FS_QUADPOINTSF_SIZE = 8 * 4; // eight floats, 32 bytes
2634
+ const pdf = this.pdfiumModule.pdfium;
2635
+ const count = this.pdfiumModule.FPDFAnnot_CountAttachmentPoints(annotPtr);
2636
+ const buf = this.malloc(FS_QUADPOINTSF_SIZE);
2637
+ /** write one quad into `buf` in annotation space */
2638
+ const writeQuad = (r) => {
2639
+ const q = models.rectToQuad(r); // TL, TR, BR, BL
2640
+ const p1 = this.convertDevicePointToPagePoint(page, q.p1);
2641
+ const p2 = this.convertDevicePointToPagePoint(page, q.p2);
2642
+ const p3 = this.convertDevicePointToPagePoint(page, q.p3); // BR
2643
+ const p4 = this.convertDevicePointToPagePoint(page, q.p4); // BL
2644
+ // PDF QuadPoints order: BL, BR, TL, TR (bottom-left, bottom-right, top-left, top-right)
2645
+ pdf.setValue(buf + 0, p1.x, 'float'); // BL (bottom-left)
2646
+ pdf.setValue(buf + 4, p1.y, 'float');
2647
+ pdf.setValue(buf + 8, p2.x, 'float'); // BR (bottom-right)
2648
+ pdf.setValue(buf + 12, p2.y, 'float');
2649
+ pdf.setValue(buf + 16, p4.x, 'float'); // TL (top-left)
2650
+ pdf.setValue(buf + 20, p4.y, 'float');
2651
+ pdf.setValue(buf + 24, p3.x, 'float'); // TR (top-right)
2652
+ pdf.setValue(buf + 28, p3.y, 'float');
2653
+ };
2654
+ /* ----------------------------------------------------------------------- */
2655
+ /* 1. overwrite the quads that already exist */
2656
+ const min = Math.min(count, rects.length);
2657
+ for (let i = 0; i < min; i++) {
2658
+ writeQuad(rects[i]);
2659
+ if (!this.pdfiumModule.FPDFAnnot_SetAttachmentPoints(annotPtr, i, buf)) {
2660
+ this.free(buf);
2661
+ return false;
2662
+ }
2663
+ }
2664
+ /* 2. append new quads if rects.length > count */
2665
+ for (let i = count; i < rects.length; i++) {
2666
+ writeQuad(rects[i]);
2667
+ if (!this.pdfiumModule.FPDFAnnot_AppendAttachmentPoints(annotPtr, buf)) {
2668
+ this.free(buf);
2669
+ return false;
2670
+ }
2671
+ }
2672
+ this.free(buf);
2673
+ return true;
2408
2674
  }
2409
2675
  /**
2410
2676
  * Read pdf text annotation
@@ -2417,30 +2683,23 @@ class PdfiumEngine {
2417
2683
  * @private
2418
2684
  */
2419
2685
  readPdfTextAnno(page, pagePtr, annotationPtr, index) {
2420
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2421
2686
  const annoRect = this.readPageAnnoRect(annotationPtr);
2422
2687
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2423
2688
  const author = this.getAnnotString(annotationPtr, 'T');
2424
2689
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2425
- const modified = this.toIsoDate(modifiedRaw);
2690
+ const modified = models.pdfDateToDate(modifiedRaw);
2426
2691
  const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2427
2692
  const state = this.getAnnotString(annotationPtr, 'State');
2428
2693
  const stateModel = this.getAnnotString(annotationPtr, 'StateModel');
2429
- const color = this.resolveAnnotationColor(annotationPtr);
2694
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2430
2695
  const inReplyToId = this.getInReplyToId(pagePtr, annotationPtr);
2431
- const popup = !inReplyToId
2432
- ? this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index)
2433
- : undefined;
2434
2696
  return {
2435
- status: models.PdfAnnotationObjectStatus.Committed,
2436
2697
  pageIndex: page.index,
2437
2698
  id: index,
2438
2699
  type: models.PdfAnnotationSubtype.TEXT,
2439
2700
  contents,
2440
- color,
2701
+ ...webAlphaColor,
2441
2702
  rect,
2442
- popup,
2443
- appearances,
2444
2703
  inReplyToId,
2445
2704
  author,
2446
2705
  modified,
@@ -2459,16 +2718,13 @@ class PdfiumEngine {
2459
2718
  * @private
2460
2719
  */
2461
2720
  readPdfFreeTextAnno(page, pagePtr, annotationPtr, index) {
2462
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2463
2721
  const annoRect = this.readPageAnnoRect(annotationPtr);
2464
2722
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2465
2723
  const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2466
2724
  const author = this.getAnnotString(annotationPtr, 'T');
2467
2725
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2468
- const modified = this.toIsoDate(modifiedRaw);
2469
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2726
+ const modified = models.pdfDateToDate(modifiedRaw);
2470
2727
  return {
2471
- status: models.PdfAnnotationObjectStatus.Committed,
2472
2728
  pageIndex: page.index,
2473
2729
  id: index,
2474
2730
  type: models.PdfAnnotationSubtype.FREETEXT,
@@ -2476,8 +2732,6 @@ class PdfiumEngine {
2476
2732
  author,
2477
2733
  modified,
2478
2734
  rect,
2479
- popup,
2480
- appearances,
2481
2735
  };
2482
2736
  }
2483
2737
  /**
@@ -2497,13 +2751,12 @@ class PdfiumEngine {
2497
2751
  if (!linkPtr) {
2498
2752
  return;
2499
2753
  }
2500
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2501
2754
  const annoRect = this.readPageAnnoRect(annotationPtr);
2502
2755
  const { left, top, right, bottom } = annoRect;
2503
2756
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, annoRect);
2504
2757
  const author = this.getAnnotString(annotationPtr, 'T');
2505
2758
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2506
- const modified = this.toIsoDate(modifiedRaw);
2759
+ const modified = models.pdfDateToDate(modifiedRaw);
2507
2760
  const utf16Length = this.pdfiumModule.FPDFText_GetBoundedText(textPagePtr, left, top, right, bottom, 0, 0);
2508
2761
  const bytesCount = (utf16Length + 1) * 2; // include NIL
2509
2762
  const textBufferPtr = this.malloc(bytesCount);
@@ -2515,17 +2768,13 @@ class PdfiumEngine {
2515
2768
  }, () => {
2516
2769
  return this.pdfiumModule.FPDFLink_GetDest(docPtr, linkPtr);
2517
2770
  });
2518
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2519
2771
  return {
2520
- status: models.PdfAnnotationObjectStatus.Committed,
2521
2772
  pageIndex: page.index,
2522
2773
  id: index,
2523
2774
  type: models.PdfAnnotationSubtype.LINK,
2524
2775
  text,
2525
2776
  target,
2526
2777
  rect,
2527
- popup,
2528
- appearances,
2529
2778
  author,
2530
2779
  modified,
2531
2780
  };
@@ -2542,23 +2791,18 @@ class PdfiumEngine {
2542
2791
  * @private
2543
2792
  */
2544
2793
  readPdfWidgetAnno(page, pagePtr, annotationPtr, formHandle, index) {
2545
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2546
2794
  const pageRect = this.readPageAnnoRect(annotationPtr);
2547
2795
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2548
2796
  const author = this.getAnnotString(annotationPtr, 'T');
2549
2797
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2550
- const modified = this.toIsoDate(modifiedRaw);
2551
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2798
+ const modified = models.pdfDateToDate(modifiedRaw);
2552
2799
  const field = this.readPdfWidgetAnnoField(formHandle, annotationPtr);
2553
2800
  return {
2554
- status: models.PdfAnnotationObjectStatus.Committed,
2555
2801
  pageIndex: page.index,
2556
2802
  id: index,
2557
2803
  type: models.PdfAnnotationSubtype.WIDGET,
2558
2804
  rect,
2559
2805
  field,
2560
- popup,
2561
- appearances,
2562
2806
  author,
2563
2807
  modified,
2564
2808
  };
@@ -2574,21 +2818,16 @@ class PdfiumEngine {
2574
2818
  * @private
2575
2819
  */
2576
2820
  readPdfFileAttachmentAnno(page, pagePtr, annotationPtr, index) {
2577
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2578
2821
  const pageRect = this.readPageAnnoRect(annotationPtr);
2579
2822
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2580
2823
  const author = this.getAnnotString(annotationPtr, 'T');
2581
2824
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2582
- const modified = this.toIsoDate(modifiedRaw);
2583
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2825
+ const modified = models.pdfDateToDate(modifiedRaw);
2584
2826
  return {
2585
- status: models.PdfAnnotationObjectStatus.Committed,
2586
2827
  pageIndex: page.index,
2587
2828
  id: index,
2588
2829
  type: models.PdfAnnotationSubtype.FILEATTACHMENT,
2589
2830
  rect,
2590
- popup,
2591
- appearances,
2592
2831
  author,
2593
2832
  modified,
2594
2833
  };
@@ -2604,13 +2843,11 @@ class PdfiumEngine {
2604
2843
  * @private
2605
2844
  */
2606
2845
  readPdfInkAnno(page, pagePtr, annotationPtr, index) {
2607
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2608
2846
  const pageRect = this.readPageAnnoRect(annotationPtr);
2609
2847
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2610
2848
  const author = this.getAnnotString(annotationPtr, 'T');
2611
2849
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2612
- const modified = this.toIsoDate(modifiedRaw);
2613
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2850
+ const modified = models.pdfDateToDate(modifiedRaw);
2614
2851
  const inkList = [];
2615
2852
  const count = this.pdfiumModule.FPDFAnnot_GetInkListCount(annotationPtr);
2616
2853
  for (let i = 0; i < count; i++) {
@@ -2634,14 +2871,11 @@ class PdfiumEngine {
2634
2871
  inkList.push({ points });
2635
2872
  }
2636
2873
  return {
2637
- status: models.PdfAnnotationObjectStatus.Committed,
2638
2874
  pageIndex: page.index,
2639
2875
  id: index,
2640
2876
  type: models.PdfAnnotationSubtype.INK,
2641
2877
  rect,
2642
- popup,
2643
2878
  inkList,
2644
- appearances,
2645
2879
  author,
2646
2880
  modified,
2647
2881
  };
@@ -2657,23 +2891,18 @@ class PdfiumEngine {
2657
2891
  * @private
2658
2892
  */
2659
2893
  readPdfPolygonAnno(page, pagePtr, annotationPtr, index) {
2660
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2661
2894
  const pageRect = this.readPageAnnoRect(annotationPtr);
2662
2895
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2663
2896
  const author = this.getAnnotString(annotationPtr, 'T');
2664
2897
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2665
- const modified = this.toIsoDate(modifiedRaw);
2666
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2898
+ const modified = models.pdfDateToDate(modifiedRaw);
2667
2899
  const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2668
2900
  return {
2669
- status: models.PdfAnnotationObjectStatus.Committed,
2670
2901
  pageIndex: page.index,
2671
2902
  id: index,
2672
2903
  type: models.PdfAnnotationSubtype.POLYGON,
2673
2904
  rect,
2674
- popup,
2675
2905
  vertices,
2676
- appearances,
2677
2906
  author,
2678
2907
  modified,
2679
2908
  };
@@ -2689,23 +2918,18 @@ class PdfiumEngine {
2689
2918
  * @private
2690
2919
  */
2691
2920
  readPdfPolylineAnno(page, pagePtr, annotationPtr, index) {
2692
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2693
2921
  const pageRect = this.readPageAnnoRect(annotationPtr);
2694
2922
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2695
2923
  const author = this.getAnnotString(annotationPtr, 'T');
2696
2924
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2697
- const modified = this.toIsoDate(modifiedRaw);
2698
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2925
+ const modified = models.pdfDateToDate(modifiedRaw);
2699
2926
  const vertices = this.readPdfAnnoVertices(page, pagePtr, annotationPtr);
2700
2927
  return {
2701
- status: models.PdfAnnotationObjectStatus.Committed,
2702
2928
  pageIndex: page.index,
2703
2929
  id: index,
2704
2930
  type: models.PdfAnnotationSubtype.POLYLINE,
2705
2931
  rect,
2706
- popup,
2707
2932
  vertices,
2708
- appearances,
2709
2933
  author,
2710
2934
  modified,
2711
2935
  };
@@ -2721,13 +2945,11 @@ class PdfiumEngine {
2721
2945
  * @private
2722
2946
  */
2723
2947
  readPdfLineAnno(page, pagePtr, annotationPtr, index) {
2724
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2725
2948
  const pageRect = this.readPageAnnoRect(annotationPtr);
2726
2949
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2727
2950
  const author = this.getAnnotString(annotationPtr, 'T');
2728
2951
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2729
- const modified = this.toIsoDate(modifiedRaw);
2730
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2952
+ const modified = models.pdfDateToDate(modifiedRaw);
2731
2953
  const startPointPtr = this.malloc(8);
2732
2954
  const endPointPtr = this.malloc(8);
2733
2955
  this.pdfiumModule.FPDFAnnot_GetLine(annotationPtr, startPointPtr, endPointPtr);
@@ -2746,15 +2968,12 @@ class PdfiumEngine {
2746
2968
  this.free(startPointPtr);
2747
2969
  this.free(endPointPtr);
2748
2970
  return {
2749
- status: models.PdfAnnotationObjectStatus.Committed,
2750
2971
  pageIndex: page.index,
2751
2972
  id: index,
2752
2973
  type: models.PdfAnnotationSubtype.LINE,
2753
2974
  rect,
2754
- popup,
2755
2975
  startPoint,
2756
2976
  endPoint,
2757
- appearances,
2758
2977
  author,
2759
2978
  modified,
2760
2979
  };
@@ -2770,25 +2989,22 @@ class PdfiumEngine {
2770
2989
  * @private
2771
2990
  */
2772
2991
  readPdfHighlightAnno(page, pagePtr, annotationPtr, index) {
2773
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2774
2992
  const pageRect = this.readPageAnnoRect(annotationPtr);
2775
2993
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2776
- const quads = this.readAnnotationQuads(page, annotationPtr);
2777
- const color = this.resolveAnnotationColor(annotationPtr);
2778
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
2994
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
2995
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2779
2996
  const author = this.getAnnotString(annotationPtr, 'T');
2780
2997
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2781
- const modified = this.toIsoDate(modifiedRaw);
2998
+ const modified = models.pdfDateToDate(modifiedRaw);
2999
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
2782
3000
  return {
2783
- status: models.PdfAnnotationObjectStatus.Committed,
2784
3001
  pageIndex: page.index,
2785
3002
  id: index,
2786
3003
  type: models.PdfAnnotationSubtype.HIGHLIGHT,
2787
3004
  rect,
2788
- popup,
2789
- appearances,
2790
- segmentRects: quads.map(models.quadToRect),
2791
- color,
3005
+ contents,
3006
+ segmentRects,
3007
+ ...webAlphaColor,
2792
3008
  author,
2793
3009
  modified,
2794
3010
  };
@@ -2804,21 +3020,22 @@ class PdfiumEngine {
2804
3020
  * @private
2805
3021
  */
2806
3022
  readPdfUnderlineAnno(page, pagePtr, annotationPtr, index) {
2807
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2808
3023
  const pageRect = this.readPageAnnoRect(annotationPtr);
2809
3024
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2810
3025
  const author = this.getAnnotString(annotationPtr, 'T');
2811
3026
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2812
- const modified = this.toIsoDate(modifiedRaw);
2813
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3027
+ const modified = models.pdfDateToDate(modifiedRaw);
3028
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3029
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3030
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2814
3031
  return {
2815
- status: models.PdfAnnotationObjectStatus.Committed,
2816
3032
  pageIndex: page.index,
2817
3033
  id: index,
2818
3034
  type: models.PdfAnnotationSubtype.UNDERLINE,
2819
3035
  rect,
2820
- popup,
2821
- appearances,
3036
+ contents,
3037
+ segmentRects,
3038
+ ...webAlphaColor,
2822
3039
  author,
2823
3040
  modified,
2824
3041
  };
@@ -2834,21 +3051,22 @@ class PdfiumEngine {
2834
3051
  * @private
2835
3052
  */
2836
3053
  readPdfStrikeOutAnno(page, pagePtr, annotationPtr, index) {
2837
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2838
3054
  const pageRect = this.readPageAnnoRect(annotationPtr);
2839
3055
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2840
3056
  const author = this.getAnnotString(annotationPtr, 'T');
2841
3057
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2842
- const modified = this.toIsoDate(modifiedRaw);
2843
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3058
+ const modified = models.pdfDateToDate(modifiedRaw);
3059
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3060
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3061
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2844
3062
  return {
2845
- status: models.PdfAnnotationObjectStatus.Committed,
2846
3063
  pageIndex: page.index,
2847
3064
  id: index,
2848
3065
  type: models.PdfAnnotationSubtype.STRIKEOUT,
2849
3066
  rect,
2850
- popup,
2851
- appearances,
3067
+ contents,
3068
+ segmentRects,
3069
+ ...webAlphaColor,
2852
3070
  author,
2853
3071
  modified,
2854
3072
  };
@@ -2864,21 +3082,22 @@ class PdfiumEngine {
2864
3082
  * @private
2865
3083
  */
2866
3084
  readPdfSquigglyAnno(page, pagePtr, annotationPtr, index) {
2867
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2868
3085
  const pageRect = this.readPageAnnoRect(annotationPtr);
2869
3086
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2870
3087
  const author = this.getAnnotString(annotationPtr, 'T');
2871
3088
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2872
- const modified = this.toIsoDate(modifiedRaw);
2873
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3089
+ const modified = models.pdfDateToDate(modifiedRaw);
3090
+ const segmentRects = this.getQuadPointsAnno(page, annotationPtr);
3091
+ const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3092
+ const webAlphaColor = this.resolveAnnotationColor(annotationPtr);
2874
3093
  return {
2875
- status: models.PdfAnnotationObjectStatus.Committed,
2876
3094
  pageIndex: page.index,
2877
3095
  id: index,
2878
3096
  type: models.PdfAnnotationSubtype.SQUIGGLY,
2879
3097
  rect,
2880
- popup,
2881
- appearances,
3098
+ contents,
3099
+ segmentRects,
3100
+ ...webAlphaColor,
2882
3101
  author,
2883
3102
  modified,
2884
3103
  };
@@ -2894,21 +3113,16 @@ class PdfiumEngine {
2894
3113
  * @private
2895
3114
  */
2896
3115
  readPdfCaretAnno(page, pagePtr, annotationPtr, index) {
2897
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2898
3116
  const pageRect = this.readPageAnnoRect(annotationPtr);
2899
3117
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2900
3118
  const author = this.getAnnotString(annotationPtr, 'T');
2901
3119
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2902
- const modified = this.toIsoDate(modifiedRaw);
2903
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3120
+ const modified = models.pdfDateToDate(modifiedRaw);
2904
3121
  return {
2905
- status: models.PdfAnnotationObjectStatus.Committed,
2906
3122
  pageIndex: page.index,
2907
3123
  id: index,
2908
3124
  type: models.PdfAnnotationSubtype.CARET,
2909
3125
  rect,
2910
- popup,
2911
- appearances,
2912
3126
  author,
2913
3127
  modified,
2914
3128
  };
@@ -2925,13 +3139,11 @@ class PdfiumEngine {
2925
3139
  * @private
2926
3140
  */
2927
3141
  readPdfStampAnno(docPtr, page, pagePtr, annotationPtr, index) {
2928
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
2929
3142
  const pageRect = this.readPageAnnoRect(annotationPtr);
2930
3143
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
2931
3144
  const author = this.getAnnotString(annotationPtr, 'T');
2932
3145
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
2933
- const modified = this.toIsoDate(modifiedRaw);
2934
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3146
+ const modified = models.pdfDateToDate(modifiedRaw);
2935
3147
  const contents = [];
2936
3148
  const objectCount = this.pdfiumModule.FPDFAnnot_GetObjectCount(annotationPtr);
2937
3149
  for (let i = 0; i < objectCount; i++) {
@@ -2942,14 +3154,11 @@ class PdfiumEngine {
2942
3154
  }
2943
3155
  }
2944
3156
  return {
2945
- status: models.PdfAnnotationObjectStatus.Committed,
2946
3157
  pageIndex: page.index,
2947
3158
  id: index,
2948
3159
  type: models.PdfAnnotationSubtype.STAMP,
2949
3160
  rect,
2950
- popup,
2951
3161
  contents,
2952
- appearances,
2953
3162
  author,
2954
3163
  modified,
2955
3164
  };
@@ -3129,21 +3338,16 @@ class PdfiumEngine {
3129
3338
  * @private
3130
3339
  */
3131
3340
  readPdfCircleAnno(page, pagePtr, annotationPtr, index) {
3132
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3133
3341
  const pageRect = this.readPageAnnoRect(annotationPtr);
3134
3342
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3135
3343
  const author = this.getAnnotString(annotationPtr, 'T');
3136
3344
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3137
- const modified = this.toIsoDate(modifiedRaw);
3138
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3345
+ const modified = models.pdfDateToDate(modifiedRaw);
3139
3346
  return {
3140
- status: models.PdfAnnotationObjectStatus.Committed,
3141
3347
  pageIndex: page.index,
3142
3348
  id: index,
3143
3349
  type: models.PdfAnnotationSubtype.CIRCLE,
3144
3350
  rect,
3145
- popup,
3146
- appearances,
3147
3351
  author,
3148
3352
  modified,
3149
3353
  };
@@ -3159,21 +3363,16 @@ class PdfiumEngine {
3159
3363
  * @private
3160
3364
  */
3161
3365
  readPdfSquareAnno(page, pagePtr, annotationPtr, index) {
3162
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3163
3366
  const pageRect = this.readPageAnnoRect(annotationPtr);
3164
3367
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3165
3368
  const author = this.getAnnotString(annotationPtr, 'T');
3166
3369
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3167
- const modified = this.toIsoDate(modifiedRaw);
3168
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3370
+ const modified = models.pdfDateToDate(modifiedRaw);
3169
3371
  return {
3170
- status: models.PdfAnnotationObjectStatus.Committed,
3171
3372
  pageIndex: page.index,
3172
3373
  id: index,
3173
3374
  type: models.PdfAnnotationSubtype.SQUARE,
3174
3375
  rect,
3175
- popup,
3176
- appearances,
3177
3376
  author,
3178
3377
  modified,
3179
3378
  };
@@ -3190,21 +3389,16 @@ class PdfiumEngine {
3190
3389
  * @private
3191
3390
  */
3192
3391
  readPdfAnno(page, pagePtr, type, annotationPtr, index) {
3193
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3194
3392
  const pageRect = this.readPageAnnoRect(annotationPtr);
3195
3393
  const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3196
3394
  const author = this.getAnnotString(annotationPtr, 'T');
3197
3395
  const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3198
- const modified = this.toIsoDate(modifiedRaw);
3199
- const popup = this.readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index);
3396
+ const modified = models.pdfDateToDate(modifiedRaw);
3200
3397
  return {
3201
- status: models.PdfAnnotationObjectStatus.Committed,
3202
3398
  pageIndex: page.index,
3203
3399
  id: index,
3204
3400
  type,
3205
3401
  rect,
3206
- popup,
3207
- appearances,
3208
3402
  author,
3209
3403
  modified,
3210
3404
  };
@@ -3226,25 +3420,6 @@ class PdfiumEngine {
3226
3420
  const idx = this.pdfiumModule.FPDFPage_GetAnnotIndex(pagePtr, parentPtr);
3227
3421
  return idx >= 0 ? idx : undefined;
3228
3422
  }
3229
- /**
3230
- * Parse a PDF date string **D:YYYYMMDDHHmmSSOHH'mm'** to ISO-8601.
3231
- *
3232
- * Returns `undefined` if the input is malformed.
3233
- *
3234
- * @private
3235
- */
3236
- toIsoDate(pdfDate) {
3237
- if (!pdfDate?.startsWith('D:'))
3238
- return;
3239
- // Minimal parse – ignore timezone for brevity
3240
- const y = pdfDate.substring(2, 6);
3241
- const m = pdfDate.substring(6, 8) || '01';
3242
- const d = pdfDate.substring(8, 10) || '01';
3243
- const H = pdfDate.substring(10, 12) || '00';
3244
- const M = pdfDate.substring(12, 14) || '00';
3245
- const S = pdfDate.substring(14, 16) || '00';
3246
- return `${y}-${m}-${d}T${H}:${M}:${S}`;
3247
- }
3248
3423
  /**
3249
3424
  * Fetch a string value (`/T`, `/M`, `/State`, …) from an annotation.
3250
3425
  *
@@ -3264,41 +3439,19 @@ class PdfiumEngine {
3264
3439
  return value || undefined;
3265
3440
  }
3266
3441
  /**
3267
- * Read linked popup of pdf annotation
3268
- * @param page - pdf page infor
3269
- * @param pagePtr - pointer to pdf page object
3270
- * @param annotationPtr - pointer to pdf annotation
3271
- * @param index - index of annotation in the pdf page
3272
- * @returns pdf popup linked to annotation
3442
+ * Set a string value (`/T`, `/M`, `/State`, …) to an annotation.
3443
+ *
3444
+ * @returns `true` if the operation was successful
3273
3445
  *
3274
3446
  * @private
3275
3447
  */
3276
- readPdfAnnoLinkedPopup(page, pagePtr, annotationPtr, index) {
3277
- const appearances = this.readPageAnnoAppearanceStreams(annotationPtr);
3278
- const popupAnnotationPtr = this.pdfiumModule.FPDFAnnot_GetLinkedAnnot(annotationPtr, 'Popup');
3279
- if (!popupAnnotationPtr) {
3280
- return;
3281
- }
3282
- const pageRect = this.readPageAnnoRect(popupAnnotationPtr);
3283
- const rect = this.convertPageRectToDeviceRect(page, pagePtr, pageRect);
3284
- const author = this.getAnnotString(annotationPtr, 'T');
3285
- const modifiedRaw = this.getAnnotString(annotationPtr, 'M');
3286
- const modified = this.toIsoDate(modifiedRaw);
3287
- const contents = this.getAnnotString(annotationPtr, 'Contents') || '';
3288
- const open = this.getAnnotString(annotationPtr, 'Open') || 'false';
3289
- this.pdfiumModule.FPDFPage_CloseAnnot(popupAnnotationPtr);
3290
- return {
3291
- status: models.PdfAnnotationObjectStatus.Committed,
3292
- pageIndex: page.index,
3293
- id: index,
3294
- type: models.PdfAnnotationSubtype.POPUP,
3295
- rect,
3296
- contents,
3297
- open: open === 'true',
3298
- appearances,
3299
- author,
3300
- modified,
3301
- };
3448
+ setAnnotString(annotationPtr, key, value) {
3449
+ const bytes = 2 * (value.length + 1);
3450
+ const ptr = this.malloc(bytes);
3451
+ this.pdfiumModule.pdfium.stringToUTF16(value, ptr, bytes);
3452
+ const ok = this.pdfiumModule.FPDFAnnot_SetStringValue(annotationPtr, key, ptr);
3453
+ this.free(ptr);
3454
+ return ok;
3302
3455
  }
3303
3456
  /**
3304
3457
  * Read vertices of pdf annotation
@@ -3739,6 +3892,69 @@ class PdfiumEngine {
3739
3892
  this.free(bufferPtr);
3740
3893
  return ap;
3741
3894
  }
3895
+ /**
3896
+ * Change the visible colour (and opacity) of an existing annotation.
3897
+ *
3898
+ * For markup annotations (highlight / underline / strikeout / squiggly) we
3899
+ * first clear the AP dictionary entry, otherwise the stored appearance stream
3900
+ * will override the new tint. For all other sub-types we keep the existing
3901
+ * AP so custom artwork isn't lost.
3902
+ *
3903
+ * @param doc logical document object
3904
+ * @param page logical page object
3905
+ * @param annotation the annotation we want to recolour
3906
+ * @param colour RGBA tuple (0-255 per channel)
3907
+ * @param which 0 = stroke/fill colour (PDFium's "colourType" param)
3908
+ *
3909
+ * @returns `true` when the operation succeeded
3910
+ */
3911
+ updateAnnotationColor(doc, page, annotation, color, which = 0) {
3912
+ this.logger.debug(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', doc, page, annotation, color, which);
3913
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'Begin', doc.id);
3914
+ const task = models.PdfTaskHelper.create();
3915
+ try {
3916
+ /* 1 ── sanity & native handles ────────────────────────────────────────── */
3917
+ const ctx = this.cache.getContext(doc.id);
3918
+ if (!ctx) {
3919
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
3920
+ this.logger.warn(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: doc closed');
3921
+ task.resolve(false);
3922
+ return task;
3923
+ }
3924
+ const pageCtx = ctx.acquirePage(page.index);
3925
+ const annotPtr = this.pdfiumModule.FPDFPage_GetAnnot(pageCtx.pagePtr, annotation.id);
3926
+ if (!annotPtr) {
3927
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
3928
+ this.logger.warn(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: annot not found');
3929
+ pageCtx.release();
3930
+ task.resolve(false);
3931
+ return task;
3932
+ }
3933
+ /* 2 ── wipe AP if it's a simple markup annotation ─────────────────────── */
3934
+ const shouldClearAP = annotation.type === models.PdfAnnotationSubtype.HIGHLIGHT ||
3935
+ annotation.type === models.PdfAnnotationSubtype.UNDERLINE ||
3936
+ annotation.type === models.PdfAnnotationSubtype.STRIKEOUT ||
3937
+ annotation.type === models.PdfAnnotationSubtype.SQUIGGLY;
3938
+ const ok = this.setAnnotationColor(annotPtr, color, shouldClearAP, which);
3939
+ /* 4 ── regenerate appearance & clean-up ───────────────────────────────── */
3940
+ if (ok) {
3941
+ this.pdfiumModule.FPDFPage_GenerateContent(pageCtx.pagePtr);
3942
+ }
3943
+ this.pdfiumModule.FPDFPage_CloseAnnot(annotPtr);
3944
+ pageCtx.release();
3945
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
3946
+ task.resolve(!!ok);
3947
+ }
3948
+ catch (error) {
3949
+ this.logger.perf(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor', 'End', doc.id);
3950
+ this.logger.error(LOG_SOURCE, LOG_CATEGORY, 'setAnnotationColor: error', error);
3951
+ task.reject({
3952
+ code: models.PdfErrorCode.Unknown,
3953
+ message: `Failed to set annotation color: ${error instanceof Error ? error.message : String(error)}`,
3954
+ });
3955
+ }
3956
+ return task;
3957
+ }
3742
3958
  /**
3743
3959
  * Set the rect of specified annotation
3744
3960
  * @param page - page info that the annotation is belonged to
@@ -4026,11 +4242,11 @@ class PdfiumEngine {
4026
4242
  }
4027
4243
  }
4028
4244
 
4029
- async function createPdfiumEngine(wasmUrl) {
4245
+ async function createPdfiumEngine(wasmUrl, logger) {
4030
4246
  const response = await fetch(wasmUrl);
4031
4247
  const wasmBinary = await response.arrayBuffer();
4032
4248
  const wasmModule = await pdfium.init({ wasmBinary });
4033
- return new PdfiumEngine(wasmModule);
4249
+ return new PdfiumEngine(wasmModule, logger);
4034
4250
  }
4035
4251
 
4036
4252
  exports.createPdfiumEngine = createPdfiumEngine;