kviewer 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +25 -4
  3. package/dist/runtime/annotation/engine/config.d.ts +1 -1
  4. package/dist/runtime/annotation/engine/config.js +2 -2
  5. package/dist/runtime/annotation/engine/painter.d.ts +1 -1
  6. package/dist/runtime/annotation/engine/utils.d.ts +2 -1
  7. package/dist/runtime/annotation/engine/utils.js +3 -7
  8. package/dist/runtime/annotation/pdf-export/export.js +63 -9
  9. package/dist/runtime/annotation/pdf-export/parse_circle.js +3 -2
  10. package/dist/runtime/annotation/pdf-export/parse_freetext.js +18 -8
  11. package/dist/runtime/annotation/pdf-export/parse_highlight.js +27 -29
  12. package/dist/runtime/annotation/pdf-export/parse_ink.js +5 -3
  13. package/dist/runtime/annotation/pdf-export/parse_line.js +5 -3
  14. package/dist/runtime/annotation/pdf-export/parse_polyline.js +6 -4
  15. package/dist/runtime/annotation/pdf-export/parse_square.js +3 -2
  16. package/dist/runtime/annotation/pdf-export/parse_stamp.js +16 -9
  17. package/dist/runtime/annotation/pdf-export/parse_strikeout.js +11 -10
  18. package/dist/runtime/annotation/pdf-export/parse_text.js +3 -1
  19. package/dist/runtime/annotation/pdf-export/parse_underline.js +11 -10
  20. package/dist/runtime/annotation/pdf-export/parse_utils.d.ts +11 -0
  21. package/dist/runtime/annotation/pdf-export/parse_utils.js +12 -0
  22. package/dist/runtime/annotation/pdf-import/decode.d.ts +2 -1
  23. package/dist/runtime/annotation/pdf-import/decode.js +6 -5
  24. package/dist/runtime/annotation/pdf-import/decode_circle.js +1 -1
  25. package/dist/runtime/annotation/pdf-import/decode_freetext.js +1 -1
  26. package/dist/runtime/annotation/pdf-import/decode_highlight.js +1 -1
  27. package/dist/runtime/annotation/pdf-import/decode_ink.js +1 -1
  28. package/dist/runtime/annotation/pdf-import/decode_line.js +1 -1
  29. package/dist/runtime/annotation/pdf-import/decode_square.js +1 -1
  30. package/dist/runtime/annotation/pdf-import/decode_stamp.js +1 -1
  31. package/dist/runtime/annotation/pdf-import/decode_text.js +1 -1
  32. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.d.ts +3 -2
  33. package/dist/runtime/annotation/pdf-import/extract_stamp_appearance.js +7 -15
  34. package/dist/runtime/annotation/pdf-import/transform.d.ts +85 -0
  35. package/dist/runtime/annotation/pdf-import/transform.js +81 -0
  36. package/dist/runtime/annotation/pdf-import/types.d.ts +5 -1
  37. package/dist/runtime/annotation/pdf-import/utils.d.ts +6 -5
  38. package/dist/runtime/annotation/pdf-import/utils.js +53 -38
  39. package/dist/runtime/components/AnnotationToolbar.vue +16 -0
  40. package/dist/runtime/components/FormFieldLayer.d.vue.ts +4 -7
  41. package/dist/runtime/components/FormFieldLayer.vue +5 -10
  42. package/dist/runtime/components/FormFieldLayer.vue.d.ts +4 -7
  43. package/dist/runtime/components/PdfPage.vue +1 -6
  44. package/dist/runtime/components/form-fields/FormFieldWrapper.d.vue.ts +5 -6
  45. package/dist/runtime/components/form-fields/FormFieldWrapper.vue +8 -14
  46. package/dist/runtime/components/form-fields/FormFieldWrapper.vue.d.ts +5 -6
  47. package/dist/runtime/composables/usePageVirtualization.d.ts +6 -0
  48. package/dist/runtime/composables/usePageVirtualization.js +8 -2
  49. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
+ import { convertCanvasPointToPdf, pageTransformFromPdfLibPage } from "../pdf-import/transform.js";
3
4
  import { AnnotationParser } from "./parse.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
- function buildQuadPoints(konvaString, pageHeight) {
6
+ function buildQuadPoints(konvaString, t) {
6
7
  const konvaGroup = JSON.parse(konvaString);
7
8
  const rects = (konvaGroup.children ?? []).filter((item) => item.className === "Rect");
8
9
  const quadPoints = [];
@@ -11,11 +12,11 @@ function buildQuadPoints(konvaString, pageHeight) {
11
12
  const y = rect.attrs?.y ?? 0;
12
13
  const width = rect.attrs?.width ?? 0;
13
14
  const height = rect.attrs?.height ?? 0;
14
- const x1 = x;
15
- const y1 = pageHeight - y;
16
- const x2 = x + width;
17
- const y2 = pageHeight - (y + height);
18
- quadPoints.push(x1, y1, x2, y1, x1, y2, x2, y2);
15
+ const tl = convertCanvasPointToPdf(t, x, y);
16
+ const tr = convertCanvasPointToPdf(t, x + width, y);
17
+ const bl = convertCanvasPointToPdf(t, x, y + height);
18
+ const br = convertCanvasPointToPdf(t, x + width, y + height);
19
+ quadPoints.push(tl.x, tl.y, tr.x, tr.y, bl.x, bl.y, br.x, br.y);
19
20
  }
20
21
  return quadPoints;
21
22
  }
@@ -23,12 +24,12 @@ export class StrikeOutParser extends AnnotationParser {
23
24
  async parse() {
24
25
  const { annotation, page, pdfDoc } = this;
25
26
  const context = pdfDoc.context;
26
- const pageHeight = page.getHeight();
27
- const quadPoints = buildQuadPoints(annotation.konvaString, pageHeight);
27
+ const pageTransform = pageTransformFromPdfLibPage(page);
28
+ const quadPoints = buildQuadPoints(annotation.konvaString, pageTransform);
28
29
  const mainAnn = context.obj({
29
30
  Type: PDFName.of("Annot"),
30
31
  Subtype: PDFName.of("StrikeOut"),
31
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
32
+ Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageTransform),
32
33
  QuadPoints: quadPoints,
33
34
  C: rgbToPdfColor(annotation.color || void 0),
34
35
  T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
@@ -42,7 +43,7 @@ export class StrikeOutParser extends AnnotationParser {
42
43
  const replyAnn = context.obj({
43
44
  Type: PDFName.of("Annot"),
44
45
  Subtype: PDFName.of("Text"),
45
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
46
+ Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageTransform),
46
47
  Contents: stringToPDFHexString(comment.content),
47
48
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
48
49
  M: PDFString.of(comment.date || ""),
@@ -1,13 +1,15 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
+ import { pageTransformFromPdfLibPage } from "../pdf-import/transform.js";
3
4
  import { AnnotationParser } from "./parse.js";
4
5
  import { computeBoundsFromKonvaString } from "./parse_utils.js";
5
6
  const UNKNOWN_USER = "Unknown user";
6
7
  export class TextParser extends AnnotationParser {
7
8
  async parse() {
8
9
  const { annotation, page, pdfDoc } = this;
10
+ const pageTransform = pageTransformFromPdfLibPage(page);
9
11
  const bounds = computeBoundsFromKonvaString(annotation.konvaString);
10
- const rect = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, page.getHeight());
12
+ const rect = convertKonvaRectToPdfRect(bounds ?? annotation.konvaClientRect, pageTransform);
11
13
  const context = pdfDoc.context;
12
14
  const mainAnn = context.obj({
13
15
  Type: PDFName.of("Annot"),
@@ -1,8 +1,9 @@
1
1
  import { PDFName, PDFString } from "pdf-lib";
2
2
  import { convertKonvaRectToPdfRect, rgbToPdfColor, stringToPDFHexString } from "../engine/utils.js";
3
+ import { convertCanvasPointToPdf, pageTransformFromPdfLibPage } from "../pdf-import/transform.js";
3
4
  import { AnnotationParser } from "./parse.js";
4
5
  const UNKNOWN_USER = "Unknown user";
5
- function buildQuadPoints(konvaString, pageHeight) {
6
+ function buildQuadPoints(konvaString, t) {
6
7
  const konvaGroup = JSON.parse(konvaString);
7
8
  const rects = (konvaGroup.children ?? []).filter((item) => item.className === "Rect");
8
9
  const quadPoints = [];
@@ -11,11 +12,11 @@ function buildQuadPoints(konvaString, pageHeight) {
11
12
  const y = rect.attrs?.y ?? 0;
12
13
  const width = rect.attrs?.width ?? 0;
13
14
  const height = rect.attrs?.height ?? 0;
14
- const x1 = x;
15
- const y1 = pageHeight - y;
16
- const x2 = x + width;
17
- const y2 = pageHeight - (y + height);
18
- quadPoints.push(x1, y1, x2, y1, x1, y2, x2, y2);
15
+ const tl = convertCanvasPointToPdf(t, x, y);
16
+ const tr = convertCanvasPointToPdf(t, x + width, y);
17
+ const bl = convertCanvasPointToPdf(t, x, y + height);
18
+ const br = convertCanvasPointToPdf(t, x + width, y + height);
19
+ quadPoints.push(tl.x, tl.y, tr.x, tr.y, bl.x, bl.y, br.x, br.y);
19
20
  }
20
21
  return quadPoints;
21
22
  }
@@ -23,12 +24,12 @@ export class UnderlineParser extends AnnotationParser {
23
24
  async parse() {
24
25
  const { annotation, page, pdfDoc } = this;
25
26
  const context = pdfDoc.context;
26
- const pageHeight = page.getHeight();
27
- const quadPoints = buildQuadPoints(annotation.konvaString, pageHeight);
27
+ const pageTransform = pageTransformFromPdfLibPage(page);
28
+ const quadPoints = buildQuadPoints(annotation.konvaString, pageTransform);
28
29
  const mainAnn = context.obj({
29
30
  Type: PDFName.of("Annot"),
30
31
  Subtype: PDFName.of("Underline"),
31
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
32
+ Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageTransform),
32
33
  QuadPoints: quadPoints,
33
34
  C: rgbToPdfColor(annotation.color || void 0),
34
35
  T: stringToPDFHexString(annotation.title || UNKNOWN_USER),
@@ -42,7 +43,7 @@ export class UnderlineParser extends AnnotationParser {
42
43
  const replyAnn = context.obj({
43
44
  Type: PDFName.of("Annot"),
44
45
  Subtype: PDFName.of("Text"),
45
- Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageHeight),
46
+ Rect: convertKonvaRectToPdfRect(annotation.konvaClientRect, pageTransform),
46
47
  Contents: stringToPDFHexString(comment.content),
47
48
  T: stringToPDFHexString(comment.title || UNKNOWN_USER),
48
49
  M: PDFString.of(comment.date || ""),
@@ -24,3 +24,14 @@ export declare function computeBoundsFromKonvaString(konvaString: string): {
24
24
  width: number;
25
25
  height: number;
26
26
  } | null;
27
+ /**
28
+ * `/Matrix` for an annotation appearance form XObject that should read
29
+ * upright on a `/Rotate` page. Form-local content is drawn in display-
30
+ * upright orientation inside `/BBox = [0, 0, W, H]`; the returned matrix
31
+ * rotates that by `rotation` degrees CCW so the page's intrinsic clockwise
32
+ * `/Rotate` cancels out at view time. Translation values place the rotated
33
+ * bounding box at the origin so the PDF viewer can scale it 1:1 into
34
+ * `/Rect` (whose user-space dims will be `(W, H)` for 0°/180° and
35
+ * `(H, W)` for 90°/270° — matching the rotated form box).
36
+ */
37
+ export declare function formAppearanceMatrix(rotation: number, W: number, H: number): [number, number, number, number, number, number];
@@ -52,3 +52,15 @@ export function computeBoundsFromKonvaString(konvaString) {
52
52
  height: maxY - minY
53
53
  };
54
54
  }
55
+ export function formAppearanceMatrix(rotation, W, H) {
56
+ switch (rotation) {
57
+ case 90:
58
+ return [0, 1, -1, 0, H, 0];
59
+ case 180:
60
+ return [-1, 0, 0, -1, W, H];
61
+ case 270:
62
+ return [0, -1, 1, 0, 0, W];
63
+ default:
64
+ return [1, 0, 0, 1, 0, 0];
65
+ }
66
+ }
@@ -1,5 +1,6 @@
1
1
  import type { PDFDocumentProxy } from 'pdfjs-dist';
2
2
  import { type IAnnotationStore } from '../engine/types.js';
3
+ import { type PageTransform } from './transform.js';
3
4
  import { type StampAppearance } from './types.js';
4
5
  export interface DecodePdfAnnotationsResult {
5
6
  annotations: IAnnotationStore[];
@@ -16,6 +17,6 @@ export declare function filterCollidingAnnotations(annotations: IAnnotationStore
16
17
  annotations: IAnnotationStore[];
17
18
  skipped: number;
18
19
  };
19
- export declare function decodePdfPageAnnotations(pageAnnotations: unknown[], pageNumber: number, pageHeight: number, options?: DecodePageOptions): DecodePdfAnnotationsResult;
20
+ export declare function decodePdfPageAnnotations(pageAnnotations: unknown[], pageNumber: number, pageTransform: PageTransform, options?: DecodePageOptions): DecodePdfAnnotationsResult;
20
21
  export declare function decodePdfAnnotationsFromDocument(pdfDoc: PDFDocumentProxy): Promise<DecodePdfAnnotationsResult>;
21
22
  export {};
@@ -8,6 +8,7 @@ import { decodeStampAnnotation } from "./decode_stamp.js";
8
8
  import { decodeSquareAnnotation } from "./decode_square.js";
9
9
  import { decodeTextAnnotation } from "./decode_text.js";
10
10
  import { extractStampAppearancesForPage } from "./extract_stamp_appearance.js";
11
+ import { pageTransformFromProxy } from "./transform.js";
11
12
  import { isRawPdfAnnotation } from "./types.js";
12
13
  const decoderMap = {
13
14
  [PdfjsAnnotationType.TEXT]: decodeTextAnnotation,
@@ -34,7 +35,7 @@ export function filterCollidingAnnotations(annotations, existingIds) {
34
35
  }
35
36
  return { annotations: result, skipped };
36
37
  }
37
- export function decodePdfPageAnnotations(pageAnnotations, pageNumber, pageHeight, options = {}) {
38
+ export function decodePdfPageAnnotations(pageAnnotations, pageNumber, pageTransform, options = {}) {
38
39
  const annotations = [];
39
40
  let skipped = 0;
40
41
  let errors = 0;
@@ -42,7 +43,7 @@ export function decodePdfPageAnnotations(pageAnnotations, pageNumber, pageHeight
42
43
  skipped += pageAnnotations.length - rawAnnotations.length;
43
44
  const context = {
44
45
  pageNumber,
45
- pageHeight,
46
+ pageTransform,
46
47
  allAnnotations: rawAnnotations,
47
48
  stampAppearances: options.stampAppearances
48
49
  };
@@ -89,7 +90,7 @@ export async function decodePdfAnnotationsFromDocument(pdfDoc) {
89
90
  for (let pageNumber = 1; pageNumber <= pdfDoc.numPages; pageNumber++) {
90
91
  try {
91
92
  const page = await pdfDoc.getPage(pageNumber);
92
- const viewport = page.getViewport({ scale: 1 });
93
+ const pageTransform = pageTransformFromProxy(page);
93
94
  const pageAnnotations = await page.getAnnotations();
94
95
  const rawAnnotations = pageAnnotations.filter(isRawPdfAnnotation);
95
96
  const hasStampAnnotations = rawAnnotations.some(
@@ -100,7 +101,7 @@ export async function decodePdfAnnotationsFromDocument(pdfDoc) {
100
101
  try {
101
102
  stampAppearances = await extractStampAppearancesForPage(
102
103
  page,
103
- viewport.height,
104
+ pageTransform,
104
105
  rawAnnotations
105
106
  );
106
107
  } catch (error) {
@@ -110,7 +111,7 @@ export async function decodePdfAnnotationsFromDocument(pdfDoc) {
110
111
  const decoded = decodePdfPageAnnotations(
111
112
  pageAnnotations,
112
113
  pageNumber,
113
- viewport.height,
114
+ pageTransform,
114
115
  { stampAppearances }
115
116
  );
116
117
  annotations.push(...decoded.annotations);
@@ -8,7 +8,7 @@ import {
8
8
  rectFromPdfRect
9
9
  } from "./utils.js";
10
10
  export function decodeCircleAnnotation(annotation, context) {
11
- const rect = rectFromPdfRect(annotation.rect, context.pageHeight);
11
+ const rect = rectFromPdfRect(annotation.rect, context.pageTransform);
12
12
  if (!rect || typeof annotation.id !== "string") return null;
13
13
  const color = getAnnotationColor(annotation, AnnotationType.CIRCLE);
14
14
  const strokeWidth = normalizedStrokeWidth(annotation, 1);
@@ -44,7 +44,7 @@ function inferTextAlign(rectWidth, textPositionX, measuredWidth) {
44
44
  return "left";
45
45
  }
46
46
  export function decodeFreeTextAnnotation(annotation, context) {
47
- const rect = rectFromPdfRect(annotation.rect, context.pageHeight);
47
+ const rect = rectFromPdfRect(annotation.rect, context.pageTransform);
48
48
  if (!rect || typeof annotation.id !== "string") return null;
49
49
  const appearance = asAppearanceData(annotation.defaultAppearanceData);
50
50
  const color = colorArrayToRgb(appearance?.fontColor) ?? getAnnotationColor(annotation, AnnotationType.FREETEXT);
@@ -75,7 +75,7 @@ export function decodeHighlightAnnotation(annotation, context) {
75
75
  const color = getAnnotationColor(annotation, type);
76
76
  const group = createGhostGroup(annotation.id);
77
77
  for (const quadSet of quadSets) {
78
- const rect = rectFromQuadPoints(quadSet, context.pageHeight);
78
+ const rect = rectFromQuadPoints(quadSet, context.pageTransform);
79
79
  if (!rect) continue;
80
80
  group.add(createSegment(type, color, rect));
81
81
  }
@@ -16,7 +16,7 @@ export function decodeInkAnnotation(annotation, context) {
16
16
  const opacity = isNumber(annotation.opacity) ? annotation.opacity : 1;
17
17
  const group = createGhostGroup(annotation.id);
18
18
  for (const list of annotation.inkLists) {
19
- const points = pointsFromInkList(list, context.pageHeight);
19
+ const points = pointsFromInkList(list, context.pageTransform);
20
20
  if (points.length < 4) continue;
21
21
  group.add(new Konva.Line({
22
22
  points,
@@ -11,7 +11,7 @@ import {
11
11
  } from "./utils.js";
12
12
  export function decodeLineAnnotation(annotation, context) {
13
13
  if (typeof annotation.id !== "string") return null;
14
- const points = linePointsFromCoordinates(annotation.lineCoordinates, context.pageHeight);
14
+ const points = linePointsFromCoordinates(annotation.lineCoordinates, context.pageTransform);
15
15
  if (!points) return null;
16
16
  const color = getAnnotationColor(annotation, AnnotationType.ARROW);
17
17
  const strokeWidth = normalizedStrokeWidth(annotation, 1);
@@ -8,7 +8,7 @@ import {
8
8
  rectFromPdfRect
9
9
  } from "./utils.js";
10
10
  export function decodeSquareAnnotation(annotation, context) {
11
- const rect = rectFromPdfRect(annotation.rect, context.pageHeight);
11
+ const rect = rectFromPdfRect(annotation.rect, context.pageTransform);
12
12
  if (!rect || typeof annotation.id !== "string") return null;
13
13
  const color = getAnnotationColor(annotation, AnnotationType.RECTANGLE);
14
14
  const strokeWidth = normalizedStrokeWidth(annotation, 1);
@@ -8,7 +8,7 @@ import {
8
8
  } from "./utils.js";
9
9
  export function decodeStampAnnotation(annotation, context) {
10
10
  if (typeof annotation.id !== "string") return null;
11
- const rect = rectFromPdfRect(annotation.rect, context.pageHeight);
11
+ const rect = rectFromPdfRect(annotation.rect, context.pageTransform);
12
12
  if (!rect) return null;
13
13
  const appearance = context.stampAppearances?.get(annotation.id);
14
14
  if (!appearance) {
@@ -9,7 +9,7 @@ import {
9
9
  } from "./utils.js";
10
10
  export function decodeTextAnnotation(annotation, context) {
11
11
  if (annotation.inReplyTo) return null;
12
- const rect = rectFromPdfRect(annotation.rect, context.pageHeight);
12
+ const rect = rectFromPdfRect(annotation.rect, context.pageTransform);
13
13
  if (!rect || typeof annotation.id !== "string") return null;
14
14
  const color = getAnnotationColor(annotation, AnnotationType.NOTE);
15
15
  const group = createGhostGroup(annotation.id);
@@ -1,4 +1,5 @@
1
1
  import type { PDFPageProxy } from 'pdfjs-dist';
2
+ import { type PageTransform } from './transform.js';
2
3
  import { type RawPdfAnnotation, type StampAppearance } from './types.js';
3
4
  export interface StampExtractionOptions {
4
5
  scale?: number;
@@ -10,7 +11,7 @@ export interface PixelBounds {
10
11
  width: number;
11
12
  height: number;
12
13
  }
13
- export declare function stampRectToPixelBounds(rect: unknown, pageHeight: number, scale: number, canvasWidth: number, canvasHeight: number): PixelBounds | null;
14
+ export declare function stampRectToPixelBounds(rect: unknown, transform: PageTransform, scale: number, canvasWidth: number, canvasHeight: number): PixelBounds | null;
14
15
  export declare function findMaskBounds(mask: Uint8Array, width: number, height: number): PixelBounds | null;
15
16
  export declare function computeDiffMask(base: Uint8ClampedArray, annotated: Uint8ClampedArray, width: number, height: number, threshold?: number): {
16
17
  mask: Uint8Array;
@@ -20,4 +21,4 @@ export declare function applyMaskOrFallback(annotated: Uint8ClampedArray, mask:
20
21
  data: Uint8ClampedArray;
21
22
  usedMask: boolean;
22
23
  };
23
- export declare function extractStampAppearancesForPage(page: PDFPageProxy, pageHeight: number, annotations: RawPdfAnnotation[], options?: StampExtractionOptions): Promise<Map<string, StampAppearance>>;
24
+ export declare function extractStampAppearancesForPage(page: PDFPageProxy, pageTransform: PageTransform, annotations: RawPdfAnnotation[], options?: StampExtractionOptions): Promise<Map<string, StampAppearance>>;
@@ -1,26 +1,18 @@
1
1
  import { PdfjsAnnotationType } from "../engine/types.js";
2
+ import { convertPdfRectToCanvas } from "./transform.js";
2
3
  import { isNumber } from "./types.js";
3
4
  const PDFJS_ANNOTATION_MODE_DISABLE = 0;
4
5
  const PDFJS_ANNOTATION_MODE_ENABLE = 1;
5
6
  const DEFAULT_RENDER_SCALE = 2;
6
7
  const DEFAULT_DIFF_THRESHOLD = 24;
7
- function normalizedPageRect(rect, pageHeight) {
8
+ function normalizedPageRect(rect, transform) {
8
9
  if (!Array.isArray(rect) || rect.length < 4) return null;
9
10
  const [rx1, ry1, rx2, ry2] = rect;
10
11
  if (!isNumber(rx1) || !isNumber(ry1) || !isNumber(rx2) || !isNumber(ry2)) return null;
11
- const x1 = Math.min(rx1, rx2);
12
- const x2 = Math.max(rx1, rx2);
13
- const y1 = Math.min(ry1, ry2);
14
- const y2 = Math.max(ry1, ry2);
15
- return {
16
- x: x1,
17
- y: pageHeight - y2,
18
- width: x2 - x1,
19
- height: y2 - y1
20
- };
12
+ return convertPdfRectToCanvas(transform, [rx1, ry1, rx2, ry2]);
21
13
  }
22
- export function stampRectToPixelBounds(rect, pageHeight, scale, canvasWidth, canvasHeight) {
23
- const normalized = normalizedPageRect(rect, pageHeight);
14
+ export function stampRectToPixelBounds(rect, transform, scale, canvasWidth, canvasHeight) {
15
+ const normalized = normalizedPageRect(rect, transform);
24
16
  if (!normalized) return null;
25
17
  const left = Math.max(0, Math.floor(normalized.x * scale));
26
18
  const top = Math.max(0, Math.floor(normalized.y * scale));
@@ -102,7 +94,7 @@ async function renderPageToCanvas(page, canvas, annotationMode, scale) {
102
94
  });
103
95
  await task.promise;
104
96
  }
105
- export async function extractStampAppearancesForPage(page, pageHeight, annotations, options = {}) {
97
+ export async function extractStampAppearancesForPage(page, pageTransform, annotations, options = {}) {
106
98
  const appearances = /* @__PURE__ */ new Map();
107
99
  const stampAnnotations = annotations.filter(
108
100
  (annotation) => annotation.annotationType === PdfjsAnnotationType.STAMP && typeof annotation.id === "string"
@@ -131,7 +123,7 @@ export async function extractStampAppearancesForPage(page, pageHeight, annotatio
131
123
  const id = annotation.id;
132
124
  const bounds = stampRectToPixelBounds(
133
125
  annotation.rect,
134
- pageHeight,
126
+ pageTransform,
135
127
  scale,
136
128
  renderWidth,
137
129
  renderHeight
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Coordinate transform from a PDF page's unrotated user space to top-left
3
+ * origin canvas space, accounting for the page's intrinsic `/Rotate`.
4
+ *
5
+ * PDF annotation coordinates (`/Rect`, `QuadPoints`, ink lists, line
6
+ * endpoints) are stored in unrotated user space regardless of the page's
7
+ * `/Rotate`. To draw them at the right spot, we apply the same
8
+ * rotation pdf.js does when it produces a viewport at scale = 1 — this is a
9
+ * minimal mirror of `PageViewport.convertToViewportPoint` for that scale.
10
+ */
11
+ export interface PageTransform {
12
+ /** Displayed page width in PDF points (rotation already applied). */
13
+ width: number;
14
+ /** Displayed page height in PDF points (rotation already applied). */
15
+ height: number;
16
+ /** Page intrinsic `/Rotate`, normalized to 0 | 90 | 180 | 270. */
17
+ rotation: number;
18
+ /** Unrotated MediaBox origin (`view[0]`, `view[1]`). Almost always 0. */
19
+ viewOffsetX: number;
20
+ viewOffsetY: number;
21
+ }
22
+ export interface CanvasPoint {
23
+ x: number;
24
+ y: number;
25
+ }
26
+ export interface CanvasRect {
27
+ x: number;
28
+ y: number;
29
+ width: number;
30
+ height: number;
31
+ }
32
+ /** Build a `PageTransform` from a pdf.js page proxy. */
33
+ export declare function pageTransformFromProxy(page: {
34
+ view: number[];
35
+ rotate: number;
36
+ }): PageTransform;
37
+ /**
38
+ * Map a point from unrotated PDF user space to canvas (top-left origin)
39
+ * space at scale = 1. Equivalent to `viewport.convertToViewportPoint(x, y)`
40
+ * when the viewport is built with `getViewport({ scale: 1 })`.
41
+ */
42
+ export declare function convertPdfPointToCanvas(t: PageTransform, x: number, y: number): CanvasPoint;
43
+ /**
44
+ * Map a PDF rect `[x1, y1, x2, y2]` (unrotated user space) to a normalized
45
+ * canvas-space rect. Rotation may swap which corner is which, so the result
46
+ * is built from `min`/`abs` of the transformed corners.
47
+ */
48
+ export declare function convertPdfRectToCanvas(t: PageTransform, rect: readonly [number, number, number, number]): CanvasRect;
49
+ /**
50
+ * Inverse of `convertPdfPointToCanvas`: map a top-left-origin canvas point
51
+ * back to unrotated PDF user space. Used by the export path to write
52
+ * annotation `/Rect` and point lists in the coordinates a PDF viewer
53
+ * expects (display content is rotated by the page's `/Rotate` at view time,
54
+ * so we must store the inverse).
55
+ */
56
+ export declare function convertCanvasPointToPdf(t: PageTransform, cx: number, cy: number): {
57
+ x: number;
58
+ y: number;
59
+ };
60
+ /**
61
+ * Inverse of `convertPdfRectToCanvas`: map a top-left-origin canvas rect
62
+ * back to an unrotated PDF user-space rect `[x1, y1, x2, y2]`. Rotation may
63
+ * swap which corner is which, so the result is normalized.
64
+ */
65
+ export declare function convertCanvasRectToPdf(t: PageTransform, rect: {
66
+ x: number;
67
+ y: number;
68
+ width: number;
69
+ height: number;
70
+ }): [number, number, number, number];
71
+ /**
72
+ * Build a `PageTransform` from a pdf-lib `PDFPage`. Note pdf-lib's
73
+ * `getWidth()` / `getHeight()` return the *unrotated* MediaBox extent, so
74
+ * we swap for the displayed dimensions on 90°/270° pages, mirroring what
75
+ * pdfjs does in its default `getViewport({ scale: 1 })` call. MediaBox
76
+ * origin is assumed (0, 0) — almost universal in real PDFs; if a future
77
+ * input needs non-zero origins, read `/MediaBox` directly.
78
+ */
79
+ export declare function pageTransformFromPdfLibPage(page: {
80
+ getWidth: () => number;
81
+ getHeight: () => number;
82
+ getRotation: () => {
83
+ angle: number;
84
+ };
85
+ }): PageTransform;
@@ -0,0 +1,81 @@
1
+ export function pageTransformFromProxy(page) {
2
+ const view = page.view;
3
+ const vx = view[0] ?? 0;
4
+ const vy = view[1] ?? 0;
5
+ const unrotatedW = (view[2] ?? 0) - vx;
6
+ const unrotatedH = (view[3] ?? 0) - vy;
7
+ const rotation = (page.rotate % 360 + 360) % 360;
8
+ const swap = rotation % 180 !== 0;
9
+ return {
10
+ width: swap ? unrotatedH : unrotatedW,
11
+ height: swap ? unrotatedW : unrotatedH,
12
+ rotation,
13
+ viewOffsetX: vx,
14
+ viewOffsetY: vy
15
+ };
16
+ }
17
+ export function convertPdfPointToCanvas(t, x, y) {
18
+ const { rotation, viewOffsetX: vx, viewOffsetY: vy } = t;
19
+ const swap = rotation % 180 !== 0;
20
+ const W = swap ? t.height : t.width;
21
+ const H = swap ? t.width : t.height;
22
+ switch (rotation) {
23
+ case 90:
24
+ return { x: y - vy, y: x - vx };
25
+ case 180:
26
+ return { x: W + vx - x, y: y - vy };
27
+ case 270:
28
+ return { x: H + vy - y, y: W + vx - x };
29
+ default:
30
+ return { x: x - vx, y: H + vy - y };
31
+ }
32
+ }
33
+ export function convertPdfRectToCanvas(t, rect) {
34
+ const a = convertPdfPointToCanvas(t, rect[0], rect[1]);
35
+ const b = convertPdfPointToCanvas(t, rect[2], rect[3]);
36
+ return {
37
+ x: Math.min(a.x, b.x),
38
+ y: Math.min(a.y, b.y),
39
+ width: Math.abs(b.x - a.x),
40
+ height: Math.abs(b.y - a.y)
41
+ };
42
+ }
43
+ export function convertCanvasPointToPdf(t, cx, cy) {
44
+ const { rotation, viewOffsetX: vx, viewOffsetY: vy } = t;
45
+ const swap = rotation % 180 !== 0;
46
+ const W = swap ? t.height : t.width;
47
+ const H = swap ? t.width : t.height;
48
+ switch (rotation) {
49
+ case 90:
50
+ return { x: cy + vx, y: cx + vy };
51
+ case 180:
52
+ return { x: W + vx - cx, y: cy + vy };
53
+ case 270:
54
+ return { x: W + vx - cy, y: H + vy - cx };
55
+ default:
56
+ return { x: cx + vx, y: H + vy - cy };
57
+ }
58
+ }
59
+ export function convertCanvasRectToPdf(t, rect) {
60
+ const a = convertCanvasPointToPdf(t, rect.x, rect.y);
61
+ const b = convertCanvasPointToPdf(t, rect.x + rect.width, rect.y + rect.height);
62
+ return [
63
+ Math.min(a.x, b.x),
64
+ Math.min(a.y, b.y),
65
+ Math.max(a.x, b.x),
66
+ Math.max(a.y, b.y)
67
+ ];
68
+ }
69
+ export function pageTransformFromPdfLibPage(page) {
70
+ const W_u = page.getWidth();
71
+ const H_u = page.getHeight();
72
+ const rotation = (page.getRotation().angle % 360 + 360) % 360;
73
+ const swap = rotation % 180 !== 0;
74
+ return {
75
+ width: swap ? H_u : W_u,
76
+ height: swap ? W_u : H_u,
77
+ rotation,
78
+ viewOffsetX: 0,
79
+ viewOffsetY: 0
80
+ };
81
+ }
@@ -1,3 +1,4 @@
1
+ import type { PageTransform } from './transform.js';
1
2
  export interface RawPdfTextObject {
2
3
  str?: unknown;
3
4
  }
@@ -43,7 +44,10 @@ export interface StampAppearance {
43
44
  }
44
45
  export interface DecodeContext {
45
46
  pageNumber: number;
46
- pageHeight: number;
47
+ /** Page coord transform — applies the page's intrinsic `/Rotate` so
48
+ * annotation `/Rect` values in unrotated user space land at the right
49
+ * canvas pixels regardless of page rotation. */
50
+ pageTransform: PageTransform;
47
51
  allAnnotations: RawPdfAnnotation[];
48
52
  stampAppearances?: Map<string, StampAppearance>;
49
53
  }
@@ -1,6 +1,7 @@
1
1
  import Konva from 'konva';
2
2
  import { type AnnotationType, type IAnnotationComment, type IAnnotationStore, type PdfjsAnnotationSubtype } from '../engine/types.js';
3
3
  import { type RawPdfAnnotation } from './types.js';
4
+ import { type PageTransform } from './transform.js';
4
5
  type Rect = {
5
6
  x: number;
6
7
  y: number;
@@ -17,8 +18,8 @@ export declare function getAnnotationText(annotation: RawPdfAnnotation): string;
17
18
  export declare function getFreeTextContent(annotation: RawPdfAnnotation): string;
18
19
  export declare function getAnnotationDate(annotation: RawPdfAnnotation): string;
19
20
  export declare function getComments(annotation: RawPdfAnnotation, allAnnotations: RawPdfAnnotation[]): IAnnotationComment[];
20
- export declare function rectFromPdfRect(input: unknown, pageHeight: number): Rect | null;
21
- export declare function pointFromPdfPoint(input: unknown, pageHeight: number): {
21
+ export declare function rectFromPdfRect(input: unknown, transform: PageTransform): Rect | null;
22
+ export declare function pointFromPdfPoint(input: unknown, transform: PageTransform): {
22
23
  x: number;
23
24
  y: number;
24
25
  } | null;
@@ -26,9 +27,9 @@ export declare function localPointFromPdfPoint(input: unknown): {
26
27
  x: number;
27
28
  y: number;
28
29
  } | null;
29
- export declare function rectFromQuadPoints(input: unknown, pageHeight: number): Rect | null;
30
- export declare function pointsFromInkList(input: unknown, pageHeight: number): number[];
31
- export declare function linePointsFromCoordinates(input: unknown, pageHeight: number): number[] | null;
30
+ export declare function rectFromQuadPoints(input: unknown, transform: PageTransform): Rect | null;
31
+ export declare function pointsFromInkList(input: unknown, transform: PageTransform): number[];
32
+ export declare function linePointsFromCoordinates(input: unknown, transform: PageTransform): number[] | null;
32
33
  export declare function normalizedStrokeWidth(annotation: RawPdfAnnotation, fallback?: number, options?: StrokeWidthOptions): number;
33
34
  export declare function dashedBorder(annotation: RawPdfAnnotation): number[];
34
35
  export declare function hasArrowEnding(value: unknown): boolean;