@reekon-tools/boldr-utils 1.22.0 → 1.24.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 (35) hide show
  1. package/dist/annotation/canvas/pdfCanvasPages.d.ts +25 -0
  2. package/dist/annotation/canvas/pdfCanvasPages.js +255 -0
  3. package/dist/annotation/data/AnnotationDataProvider.d.ts +6 -0
  4. package/dist/annotation/data/InMemoryAnnotationProvider.d.ts +6 -0
  5. package/dist/annotation/data/InMemoryAnnotationProvider.js +25 -0
  6. package/dist/annotation/data/hooks/usePdfCanvasDoc.d.ts +29 -0
  7. package/dist/annotation/data/hooks/usePdfCanvasDoc.js +344 -0
  8. package/dist/export/pdf/annots.d.ts +23 -0
  9. package/dist/export/pdf/annots.js +135 -0
  10. package/dist/export/pdf/decrypt.d.ts +21 -0
  11. package/dist/export/pdf/decrypt.js +121 -0
  12. package/dist/export/pdf/fonts.d.ts +3 -4
  13. package/dist/export/pdf/fonts.js +2 -14
  14. package/dist/export/pdf/index.d.ts +4 -2
  15. package/dist/export/pdf/index.js +3 -1
  16. package/dist/export/pdf/marks.d.ts +66 -5
  17. package/dist/export/pdf/marks.js +150 -7
  18. package/dist/export/pdf/pageMapping.d.ts +2 -0
  19. package/dist/export/pdf/pageMapping.js +10 -0
  20. package/dist/export/pdf/renderCanvasToPdf.d.ts +9 -1
  21. package/dist/export/pdf/renderCanvasToPdf.js +303 -120
  22. package/dist/export/pdf/resources.d.ts +35 -0
  23. package/dist/export/pdf/resources.js +95 -0
  24. package/dist/export/pdf/tiles.d.ts +5 -9
  25. package/dist/export/pdf/tiles.js +20 -20
  26. package/dist/export/pdf/types.d.ts +25 -1
  27. package/dist/exports.d.ts +3 -0
  28. package/dist/exports.js +3 -0
  29. package/dist/server.d.ts +2 -0
  30. package/dist/server.js +2 -0
  31. package/dist/types/firestore.d.ts +6 -0
  32. package/dist/types/firestore.js +5 -0
  33. package/dist/types/pdfCanvas.d.ts +38 -0
  34. package/dist/types/pdfCanvas.js +16 -0
  35. package/package.json +6 -1
@@ -133,15 +133,15 @@ const valueRowOps = (ctx, value, cx, cy, color, availableWidth, tileWidth) => {
133
133
  tokens.forEach((t, i) => {
134
134
  const w = widths[i];
135
135
  if (!t.fraction) {
136
- ops.push(...ctx.fonts.textOps(ctx.page, t.text, fs, color, x, baseline));
136
+ ops.push(...ctx.fonts.textOps(ctx.scope, t.text, fs, color, x, baseline));
137
137
  }
138
138
  else {
139
139
  const stackW = Math.max(ctx.fonts.width(t.fraction.num, fracFs), ctx.fonts.width(t.fraction.den, fracFs));
140
140
  const scx = x + stackW / 2;
141
141
  // Numerator above the rule, denominator below; rule at the row centre.
142
- ops.push(...ctx.fonts.textOps(ctx.page, t.fraction.num, fracFs, color, scx, cy - fracFs * 0.15, { align: 'center' }), pushGraphicsState(), ...fillStateOps(color), rectangle(x, cy - fracFs * 0.05, stackW, Math.max(fracFs * 0.08, 0.8)), fill(), popGraphicsState(), ...ctx.fonts.textOps(ctx.page, t.fraction.den, fracFs, color, scx, cy + fracFs * 0.85, { align: 'center' }));
142
+ ops.push(...ctx.fonts.textOps(ctx.scope, t.fraction.num, fracFs, color, scx, cy - fracFs * 0.15, { align: 'center' }), pushGraphicsState(), ...fillStateOps(color), rectangle(x, cy - fracFs * 0.05, stackW, Math.max(fracFs * 0.08, 0.8)), fill(), popGraphicsState(), ...ctx.fonts.textOps(ctx.scope, t.fraction.den, fracFs, color, scx, cy + fracFs * 0.85, { align: 'center' }));
143
143
  if (t.fraction.suffix) {
144
- ops.push(...ctx.fonts.textOps(ctx.page, t.fraction.suffix, fs, color, x + stackW, baseline));
144
+ ops.push(...ctx.fonts.textOps(ctx.scope, t.fraction.suffix, fs, color, x + stackW, baseline));
145
145
  }
146
146
  }
147
147
  x += w + gap;
@@ -156,7 +156,7 @@ const measurementTileOps = (ctx, c) => {
156
156
  const white = GeneralColors.white;
157
157
  // Header row: position label, bold, top-left.
158
158
  if (c.positionLabel) {
159
- ops.push(...ctx.fonts.textOps(ctx.page, c.positionLabel, 12, white, 6, 14));
159
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.positionLabel, 12, white, 6, 14));
160
160
  }
161
161
  // Value row, centred on the tile, between the two unit slots.
162
162
  const hasUnit = !!c.unit;
@@ -164,7 +164,7 @@ const measurementTileOps = (ctx, c) => {
164
164
  const valueText = (c.negative ? '-' : '') + c.value;
165
165
  ops.push(...valueRowOps(ctx, valueText, SQUARE / 2, 48, white, available, SQUARE));
166
166
  if (hasUnit) {
167
- ops.push(...ctx.fonts.textOps(ctx.page, c.unit, UNIT_FONT, white, SQUARE - UNIT_RESERVE / 2, 52, {
167
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.unit, UNIT_FONT, white, SQUARE - UNIT_RESERVE / 2, 52, {
168
168
  align: 'center',
169
169
  }));
170
170
  }
@@ -175,7 +175,7 @@ const measurementTileOps = (ctx, c) => {
175
175
  ops.push(...filledRoundedRect(4, SQUARE - 20, SQUARE - 8, 16, 3, FormulaColors.darkPurple), ...strokedRoundedRect(4, SQUARE - 20, SQUARE - 8, 16, 3, FormulaColors.purple, 1));
176
176
  }
177
177
  const text = truncate(ctx.fonts, c.label, labelSize, SQUARE - 12);
178
- ops.push(...ctx.fonts.textOps(ctx.page, text, labelSize, white, SQUARE / 2, SQUARE - 8, { align: 'center' }));
178
+ ops.push(...ctx.fonts.textOps(ctx.scope, text, labelSize, white, SQUARE / 2, SQUARE - 8, { align: 'center' }));
179
179
  }
180
180
  return ops;
181
181
  };
@@ -186,9 +186,9 @@ const emptyTileOps = (ctx, c) => {
186
186
  ...strokedRoundedRect(0, 0, SQUARE, SQUARE, CARD_RADIUS, GeneralColors.grey3, 1),
187
187
  ];
188
188
  if (c.topLeftLabel)
189
- ops.push(...ctx.fonts.textOps(ctx.page, c.topLeftLabel, 10, ink, 8, 18));
189
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.topLeftLabel, 10, ink, 8, 18));
190
190
  if (c.topRightLabel) {
191
- ops.push(...ctx.fonts.textOps(ctx.page, c.topRightLabel, 10, ink, SQUARE - 8, 18, {
191
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.topRightLabel, 10, ink, SQUARE - 8, 18, {
192
192
  align: 'right',
193
193
  }));
194
194
  }
@@ -205,7 +205,7 @@ const emptyTileOps = (ctx, c) => {
205
205
  ]), stroke(), popGraphicsState());
206
206
  if (c.bottomLabel) {
207
207
  const text = truncate(ctx.fonts, c.bottomLabel, 10, SQUARE - 16);
208
- ops.push(...ctx.fonts.textOps(ctx.page, text, 10, ink, cx, SQUARE - 10, {
208
+ ops.push(...ctx.fonts.textOps(ctx.scope, text, 10, ink, cx, SQUARE - 10, {
209
209
  align: 'center',
210
210
  }));
211
211
  }
@@ -216,17 +216,17 @@ const literalTileOps = (ctx, c) => {
216
216
  const fillColor = filled ? TileColors.activeBlue : GeneralColors.grey2;
217
217
  const ink = filled ? GeneralColors.white : GeneralColors.grey4;
218
218
  const ops = filledRoundedRect(0, 0, SQUARE, SQUARE, CARD_RADIUS, fillColor);
219
- ops.push(...ctx.fonts.textOps(ctx.page, c.topLeftLabel, 10, ink, 8, 18));
220
- ops.push(...ctx.fonts.textOps(ctx.page, c.topRightLabel, 10, ink, SQUARE - 8, 18, {
219
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.topLeftLabel, 10, ink, 8, 18));
220
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.topRightLabel, 10, ink, SQUARE - 8, 18, {
221
221
  align: 'right',
222
222
  }));
223
223
  ops.push(...filledRoundedRect(8, 28, SQUARE - 16, 40, 4, filled ? TileColors.activeBlue : GeneralColors.grey3));
224
224
  if (filled) {
225
225
  const size = ctx.fonts.fitSize(c.value, SQUARE - 24, 16, 6);
226
- ops.push(...ctx.fonts.textOps(ctx.page, c.value, size, ink, SQUARE / 2, 48 + size * 0.35, { align: 'center' }));
226
+ ops.push(...ctx.fonts.textOps(ctx.scope, c.value, size, ink, SQUARE / 2, 48 + size * 0.35, { align: 'center' }));
227
227
  }
228
228
  const bottom = truncate(ctx.fonts, c.bottomLabel, 10, SQUARE - 16);
229
- ops.push(...ctx.fonts.textOps(ctx.page, bottom, 10, ink, SQUARE / 2, SQUARE - 10, {
229
+ ops.push(...ctx.fonts.textOps(ctx.scope, bottom, 10, ink, SQUARE / 2, SQUARE - 10, {
230
230
  align: 'center',
231
231
  }));
232
232
  return ops;
@@ -273,7 +273,7 @@ const headerTileOps = (ctx, c) => {
273
273
  ops.push(...globeOps(W / 2, by + S.badgeHeight / 2, S.globeSize, S.globeColor));
274
274
  else {
275
275
  const text = truncate(ctx.fonts, b.text, S.badgeFontSize, inner - 10);
276
- ops.push(...ctx.fonts.textOps(ctx.page, text, S.badgeFontSize, S.badgeTextColor, W / 2, by + S.badgeHeight / 2 + S.badgeFontSize * 0.35, { align: 'center' }));
276
+ ops.push(...ctx.fonts.textOps(ctx.scope, text, S.badgeFontSize, S.badgeTextColor, W / 2, by + S.badgeHeight / 2 + S.badgeFontSize * 0.35, { align: 'center' }));
277
277
  }
278
278
  });
279
279
  }
@@ -287,7 +287,7 @@ const headerTileOps = (ctx, c) => {
287
287
  ops.push(...globeOps(bx + bw / 2, y + S.badgeHeight / 2, S.globeSize, S.globeColor));
288
288
  else {
289
289
  const text = truncate(ctx.fonts, b.text, S.badgeFontSize, bw - 10);
290
- ops.push(...ctx.fonts.textOps(ctx.page, text, S.badgeFontSize, S.badgeTextColor, bx + bw / 2, y + S.badgeHeight / 2 + S.badgeFontSize * 0.35, { align: 'center' }));
290
+ ops.push(...ctx.fonts.textOps(ctx.scope, text, S.badgeFontSize, S.badgeTextColor, bx + bw / 2, y + S.badgeHeight / 2 + S.badgeFontSize * 0.35, { align: 'center' }));
291
291
  }
292
292
  });
293
293
  }
@@ -295,7 +295,7 @@ const headerTileOps = (ctx, c) => {
295
295
  }
296
296
  if (d.name !== null) {
297
297
  const text = truncate(ctx.fonts, d.name, S.nameFontSize, inner);
298
- ops.push(...ctx.fonts.textOps(ctx.page, text, S.nameFontSize, S.nameColor, W / 2, y + S.nameGap + S.nameFontSize * 0.95, { align: 'center' }));
298
+ ops.push(...ctx.fonts.textOps(ctx.scope, text, S.nameFontSize, S.nameColor, W / 2, y + S.nameGap + S.nameFontSize * 0.95, { align: 'center' }));
299
299
  }
300
300
  return ops;
301
301
  };
@@ -311,14 +311,14 @@ const objectTileOps = (ctx, c) => {
311
311
  if (!thumb) {
312
312
  ops.push(...filledRoundedRect(x, y, w, h, CARD_RADIUS, GeneralColors.grey2));
313
313
  const name = truncate(ctx.fonts, c.name || 'Object removed', 9, w - 8);
314
- ops.push(...ctx.fonts.textOps(ctx.page, name, 9, GeneralColors.grey4, SQUARE / 2, SQUARE / 2 + 3, { align: 'center' }));
314
+ ops.push(...ctx.fonts.textOps(ctx.scope, name, 9, GeneralColors.grey4, SQUARE / 2, SQUARE / 2 + 3, { align: 'center' }));
315
315
  return ops;
316
316
  }
317
317
  // Thumbnail, cover-fit and clipped to the rounded card.
318
318
  const scale = Math.max(w / thumb.width, h / thumb.height);
319
319
  const iw = thumb.width * scale;
320
320
  const ih = thumb.height * scale;
321
- ops.push(pushGraphicsState(), ...roundedRectPathOps(x, y, w, h, CARD_RADIUS), clip(), endPath(), ...imageOps(thumb.name, x + (w - iw) / 2, y + (h - ih) / 2, iw, ih), popGraphicsState());
321
+ ops.push(pushGraphicsState(), ...roundedRectPathOps(x, y, w, h, CARD_RADIUS), clip(), endPath(), ...imageOps(ctx.scope.imageKey(thumb), x + (w - iw) / 2, y + (h - ih) / 2, iw, ih), popGraphicsState());
322
322
  // Footer bar by object type: a layout group gets the magenta bar with white
323
323
  // ink, a plain photo/background none, every document a white bar with dark ink.
324
324
  const type = c.fileType;
@@ -330,7 +330,7 @@ const objectTileOps = (ctx, c) => {
330
330
  if (bar) {
331
331
  ops.push(pushGraphicsState(), ...roundedRectPathOps(x, y, w, h, CARD_RADIUS), clip(), endPath(), ...fillStateOps(bar.fill), rectangle(x, y + h - OBJECT_BAR_HEIGHT, w, OBJECT_BAR_HEIGHT), fill(), popGraphicsState());
332
332
  const name = truncate(ctx.fonts, c.name, 9, w - 8);
333
- ops.push(...ctx.fonts.textOps(ctx.page, name, 9, bar.ink, SQUARE / 2, y + h - OBJECT_BAR_HEIGHT / 2 + 3, { align: 'center' }));
333
+ ops.push(...ctx.fonts.textOps(ctx.scope, name, 9, bar.ink, SQUARE / 2, y + h - OBJECT_BAR_HEIGHT / 2 + 3, { align: 'center' }));
334
334
  }
335
335
  return ops;
336
336
  };
@@ -365,7 +365,7 @@ const instructionsTileOps = (ctx, c) => {
365
365
  const lh = size * 1.25;
366
366
  const top = SQUARE / 2 - (lines.length * lh) / 2;
367
367
  lines.forEach((line, i) => {
368
- ops.push(...ctx.fonts.textOps(ctx.page, truncate(ctx.fonts, line, size, maxW), size, color, SQUARE / 2, top + i * lh + size * 0.9, { align: 'center' }));
368
+ ops.push(...ctx.fonts.textOps(ctx.scope, truncate(ctx.fonts, line, size, maxW), size, color, SQUARE / 2, top + i * lh + size * 0.9, { align: 'center' }));
369
369
  });
370
370
  return ops;
371
371
  };
@@ -1,5 +1,6 @@
1
1
  import type { GroupHeaderDescriptor } from '../../annotation/canvas/groupHeaderLayout.js';
2
2
  import type { AnnotationCanvasState, PlacedMeasurementRef } from '../../types/annotation.js';
3
+ import type { PdfCanvasData, PdfCanvasPageState } from '../../types/pdfCanvas.js';
3
4
  export interface EmbeddedImage {
4
5
  bytes: Uint8Array;
5
6
  mime: 'image/png' | 'image/jpeg';
@@ -89,13 +90,36 @@ export interface RenderCanvasToPdfInput {
89
90
  calibration?: PdfExportCalibration;
90
91
  /** TTF/OTF bytes of the primary font (Metropolis Bold in the apps). */
91
92
  primaryFont: Uint8Array;
93
+ /**
94
+ * 'annotations' (default): every layer, mark and tile becomes a PDF
95
+ * annotation object — selectable, movable and deletable in Preview /
96
+ * Acrobat, with the element's exact drawing as its appearance. 'flatten':
97
+ * everything is appended to the page content stream and cannot be edited.
98
+ */
99
+ mode?: 'annotations' | 'flatten';
100
+ }
101
+ export interface RenderPdfCanvasToPdfInput {
102
+ /** The PDF the file was uploaded from (`pdfCanvas.document.storagePath`). */
103
+ sourcePdf: Uint8Array;
104
+ /** The file-level record (`fileData.pdfCanvas`). */
105
+ pdfCanvas: PdfCanvasData;
106
+ /** Page docs by page index; a page without one is exported un-annotated. */
107
+ pageStates: Partial<Record<number, PdfCanvasPageState>>;
108
+ resolveTile: (ref: PlacedMeasurementRef) => ResolvedTileContent | undefined;
109
+ calibration?: PdfExportCalibration;
110
+ primaryFont: Uint8Array;
111
+ mode?: 'annotations' | 'flatten';
92
112
  }
93
113
  export interface RenderCanvasToPdfResult {
94
114
  pdf: Uint8Array;
95
115
  pageCount: number;
116
+ /** Annotations written across all pages (0 in flatten mode). */
117
+ annotationCount: number;
96
118
  skipped: SkippedItem[];
97
119
  }
98
120
  export declare class PdfExportError extends Error {
99
- readonly code: 'no-pdf-document' | 'page-count-mismatch' | 'page-geometry-mismatch' | 'encrypted' | 'corrupt';
121
+ readonly code: 'no-pdf-document' | 'page-count-mismatch' | 'page-geometry-mismatch'
122
+ /** The renderer was handed an encrypted PDF — run stripPdfEncryption first. */
123
+ | 'encrypted' | 'password-required' | 'password-incorrect' | 'unsupported' | 'corrupt';
100
124
  constructor(code: PdfExportError['code'], message: string);
101
125
  }
package/dist/exports.d.ts CHANGED
@@ -6,6 +6,7 @@ export { useParseMeasurement } from './utils/useParseMeasurement.js';
6
6
  export * from './types/firestore.js';
7
7
  export * from './types/layout.js';
8
8
  export * from './types/annotation.js';
9
+ export * from './types/pdfCanvas.js';
9
10
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
10
11
  export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
11
12
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
@@ -25,6 +26,7 @@ export { useAnnotationDoc, type UseAnnotationDocResult, } from './annotation/dat
25
26
  export { useAnnotationList, type UseAnnotationListResult, } from './annotation/data/hooks/useAnnotationList.js';
26
27
  export { useAnnotationMutations, type AnnotationMutations, } from './annotation/data/hooks/useAnnotationMutations.js';
27
28
  export { useAnnotationCanvasDoc, type SaveStatus, type UseAnnotationCanvasDocOptions, type UseAnnotationCanvasDocResult, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
29
+ export { usePdfCanvasDoc, type UsePdfCanvasDocOptions, type UsePdfCanvasDocResult, } from './annotation/data/hooks/usePdfCanvasDoc.js';
28
30
  export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
29
31
  export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
30
32
  export type { AnnotationCanvasHandle } from './annotation/canvas/useAnnotationCanvasState.js';
@@ -37,6 +39,7 @@ export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GRO
37
39
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, type GroupHeaderBadge, type GroupHeaderDescriptor, } from './annotation/canvas/groupHeaderLayout.js';
38
40
  export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, type ViewportApi, type ViewportState, type ZoomLimits, } from './annotation/canvas/viewport.js';
39
41
  export { DEFAULT_PDF_PAGE_GAP, DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, makePdfDocumentId, createPdfDocument, pdfPageDocRects, pdfDocumentExtent, pdfFirstPageDocRect, computeZoomFloor, type PdfDocRect, } from './annotation/canvas/pdfDocument.js';
42
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
40
43
  export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
41
44
  export { fileTileImageSource, expandFileTileToBackground, collapseBackgroundToFileTile, type FileTileImageSource, } from './annotation/canvas/fileTileSwap.js';
42
45
  export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
package/dist/exports.js CHANGED
@@ -10,6 +10,7 @@ export { useParseMeasurement } from './utils/useParseMeasurement.js';
10
10
  export * from './types/firestore.js';
11
11
  export * from './types/layout.js';
12
12
  export * from './types/annotation.js';
13
+ export * from './types/pdfCanvas.js';
13
14
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, } from './utils/tolerance.js';
14
15
  // Shared color palette (canonical source for both apps' brand colors).
15
16
  export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
@@ -37,12 +38,14 @@ export { useAnnotationDoc, } from './annotation/data/hooks/useAnnotationDoc.js';
37
38
  export { useAnnotationList, } from './annotation/data/hooks/useAnnotationList.js';
38
39
  export { useAnnotationMutations, } from './annotation/data/hooks/useAnnotationMutations.js';
39
40
  export { useAnnotationCanvasDoc, } from './annotation/data/hooks/useAnnotationCanvasDoc.js';
41
+ export { usePdfCanvasDoc, } from './annotation/data/hooks/usePdfCanvasDoc.js';
40
42
  export { hydrateCanvasState } from './annotation/data/canvasPersistence.js';
41
43
  export { InMemoryAnnotationProvider } from './annotation/data/InMemoryAnnotationProvider.js';
42
44
  export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GROUP_HEADER_COLLAPSED_WIDTH, GROUP_HEADER_COLLAPSED_HEIGHT, GROUP_HEADER_DESIGN, groupHeaderBaseDims, DEFAULT_TILE_SCALE, TILE_SCALE_MIN, TILE_SCALE_MAX, clampTileScale, resolveTileScaleFactor, resolveHeaderTileScaleFactor, resolveFileTileScaleFactor, tileDocSizeForFactor, tileScaleFromDocSize, stampTileSize, stampTileDims, isUnassociatedStamp, isFileStamp, } from './annotation/canvas/stampLayout.js';
43
45
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, } from './annotation/canvas/groupHeaderLayout.js';
44
46
  export { createViewportApi, fitToScreen, fitRectToScreen, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './annotation/canvas/viewport.js';
45
47
  export { DEFAULT_PDF_PAGE_GAP, DEFAULT_MIN_ZOOM, DEFAULT_MAX_ZOOM, makePdfDocumentId, createPdfDocument, pdfPageDocRects, pdfDocumentExtent, pdfFirstPageDocRect, computeZoomFloor, } from './annotation/canvas/pdfDocument.js';
48
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, } from './annotation/canvas/pdfCanvasPages.js';
46
49
  export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
47
50
  export { fileTileImageSource, expandFileTileToBackground, collapseBackgroundToFileTile, } from './annotation/canvas/fileTileSwap.js';
48
51
  export { createPenTool, } from './annotation/canvas/tools/penTool.js';
package/dist/server.d.ts CHANGED
@@ -5,6 +5,8 @@ export { parseMeasurement } from './utils/parseMeasurement.js';
5
5
  export * from './types/firestore.js';
6
6
  export * from './types/layout.js';
7
7
  export * from './types/annotation.js';
8
+ export * from './types/pdfCanvas.js';
9
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
8
10
  export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
9
11
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
10
12
  export { ROLE_RANK, EMPTY_GRANTS, normalizeRole, roleAtLeast, effectiveRole, canView, canEdit, canManage, canManageOrg, isGuest, groupGrants, resolveAccess, type MaybeRole, type GroupedGrants, type ResolvedAccess, } from './utils/permissions.js';
package/dist/server.js CHANGED
@@ -10,6 +10,8 @@ export { parseMeasurement } from './utils/parseMeasurement.js';
10
10
  export * from './types/firestore.js';
11
11
  export * from './types/layout.js';
12
12
  export * from './types/annotation.js';
13
+ export * from './types/pdfCanvas.js';
14
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, } from './annotation/canvas/pdfCanvasPages.js';
13
15
  export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
14
16
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
15
17
  export { ROLE_RANK, EMPTY_GRANTS, normalizeRole, roleAtLeast, effectiveRole, canView, canEdit, canManage, canManageOrg, isGuest, groupGrants, resolveAccess, } from './utils/permissions.js';
@@ -1,4 +1,5 @@
1
1
  import type { AnnotationCanvasState } from './annotation.js';
2
+ import type { PdfCanvasData } from './pdfCanvas.js';
2
3
  import type { CalculatorUnit } from '../calculator/units.js';
3
4
  interface FirestoreDoc {
4
5
  id: string;
@@ -170,6 +171,7 @@ export interface SelectedTemplate extends FirestoreDoc {
170
171
  export declare enum FileUploadType {
171
172
  Annotation = "annotation",
172
173
  Canvas = "canvas",
174
+ PdfCanvas = "pdfCanvas",
173
175
  Template = "template",
174
176
  Background = "background",
175
177
  LayoutGroup = "layoutGroup",
@@ -186,6 +188,7 @@ export interface AnnotationFileData {
186
188
  clientId: string;
187
189
  seq: number;
188
190
  };
191
+ pdfCanvas?: PdfCanvasData;
189
192
  }
190
193
  export interface CalculatorFileData {
191
194
  templateId: string;
@@ -242,6 +245,9 @@ export type FileUpload = FileUploadBase & ({
242
245
  } | {
243
246
  type: FileUploadType.Canvas;
244
247
  fileData: AnnotationFileData;
248
+ } | {
249
+ type: FileUploadType.PdfCanvas;
250
+ fileData: AnnotationFileData;
245
251
  } | {
246
252
  type: FileUploadType.Calculator;
247
253
  fileData: CalculatorFileData;
@@ -52,6 +52,11 @@ export var FileUploadType;
52
52
  // v2 Skia photo-annotation canvas. Distinct from the legacy PSPDFKit
53
53
  // `Annotation` docs so file-open routing sends it to the canvas editor.
54
54
  FileUploadType["Canvas"] = "canvas";
55
+ // A PDF annotated one page at a time with the Skia canvas (see
56
+ // types/pdfCanvas.ts). Its PDF sits at `${folderPath}/file` like a legacy
57
+ // `Annotation` upload, so a client that predates the type still opens it in
58
+ // the Nutrient viewer instead of failing.
59
+ FileUploadType["PdfCanvas"] = "pdfCanvas";
55
60
  FileUploadType["Template"] = "template";
56
61
  FileUploadType["Background"] = "background";
57
62
  FileUploadType["LayoutGroup"] = "layoutGroup";
@@ -0,0 +1,38 @@
1
+ import type { AnnotationLayer, AnnotationShape, AnnotationStroke, PlacedMeasurementRef, PlaneCalibration } from './annotation.js';
2
+ export type PdfPageRotation = 0 | 90 | 180 | 270;
3
+ export interface PdfCanvasPage {
4
+ width: number;
5
+ height: number;
6
+ rotation: PdfPageRotation;
7
+ }
8
+ export interface PdfCanvasDocument {
9
+ storagePath: string;
10
+ fileName?: string;
11
+ pageCount: number;
12
+ pages: PdfCanvasPage[];
13
+ }
14
+ export interface PdfCanvasRev {
15
+ clientId: string;
16
+ seq: number;
17
+ }
18
+ export interface PdfCanvasData {
19
+ schemaVersion: 1;
20
+ document: PdfCanvasDocument;
21
+ tileScaleFactor?: number;
22
+ tileScaleFactorMobile?: number;
23
+ headerTileScaleFactor?: number;
24
+ headerTileScaleFactorMobile?: number;
25
+ tileDocSize?: number;
26
+ annotatedPages: number[];
27
+ }
28
+ export interface PdfCanvasPageState {
29
+ schemaVersion: 1;
30
+ pageIndex: number;
31
+ layers: AnnotationLayer[];
32
+ strokes: AnnotationStroke[];
33
+ shapes: AnnotationShape[];
34
+ placedMeasurements: PlacedMeasurementRef[];
35
+ planes?: PlaneCalibration[];
36
+ updatedAt: number;
37
+ rev?: PdfCanvasRev;
38
+ }
@@ -0,0 +1,16 @@
1
+ // Schema of a PDF CANVAS file (FileUploadType.PdfCanvas): a PDF annotated one
2
+ // page at a time with the Skia canvas. Distinct from a Canvas file carrying a
3
+ // `viewport.pdfDocument` filmstrip — there every page shares one doc space;
4
+ // here EVERY PAGE IS ITS OWN CANVAS whose doc space is that page's displayed
5
+ // extent in PDF points (origin top-left, y down), so the canvas engine needs no
6
+ // page concept at all: the editor mounts the current page's state and swaps it
7
+ // on a page turn.
8
+ //
9
+ // Storage split (see pdfCanvasPages.ts for the mapping helpers):
10
+ // - `fileData.pdfCanvas` on the file doc: the small file-level record —
11
+ // which PDF, page geometry + user rotations, shared tile-scale knobs, and
12
+ // which pages have been annotated.
13
+ // - `files/{fileId}/pages/{pageIndex}`: one doc per annotated page holding
14
+ // that page's elements. Sharded so a heavy set never approaches the 1 MiB
15
+ // doc limit, a save rewrites one page, and a listener watches one page.
16
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -52,7 +52,9 @@
52
52
  "pdf-lib": "1.17.1",
53
53
  "zod": "^4.4.3"
54
54
  },
55
+ "//peerDependencies-embedpdf": "OPTIONAL, server only: `@embedpdf/pdfium` is imported lazily by exactly one module (export/pdf/decrypt.ts, reached through the ./server entry) to strip PDF encryption before pdf-lib appends to a page. The apps never resolve it.",
55
56
  "peerDependencies": {
57
+ "@embedpdf/pdfium": ">=2.15.0",
56
58
  "@shopify/react-native-skia": ">=2.5.0",
57
59
  "react": "^18.0.0 || ^19.0.0",
58
60
  "react-native": ">=0.74.0",
@@ -60,6 +62,9 @@
60
62
  "react-native-reanimated": ">=3.0.0"
61
63
  },
62
64
  "peerDependenciesMeta": {
65
+ "@embedpdf/pdfium": {
66
+ "optional": true
67
+ },
63
68
  "react-native": {
64
69
  "optional": true
65
70
  },