@reekon-tools/boldr-utils 1.26.1 → 1.27.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 (80) hide show
  1. package/dist/annotation/canvas/pdfCanvasPages.d.ts +7 -4
  2. package/dist/annotation/canvas/pdfCanvasPages.js +93 -14
  3. package/dist/annotation/canvas/tileCalibration.d.ts +16 -0
  4. package/dist/annotation/canvas/tileCalibration.js +91 -0
  5. package/dist/annotation/canvas/useAnnotationCanvasState.js +7 -67
  6. package/dist/annotation/data/hooks/usePdfCanvasDoc.js +24 -7
  7. package/dist/export/nutrient/instantJsonToPdfCanvas.d.ts +39 -0
  8. package/dist/export/nutrient/instantJsonToPdfCanvas.js +631 -0
  9. package/dist/export/pdf/index.d.ts +1 -1
  10. package/dist/export/pdf/index.js +1 -1
  11. package/dist/export/pdf/marks.js +46 -4
  12. package/dist/export/pdf/renderCanvasToPdf.d.ts +9 -0
  13. package/dist/export/pdf/renderCanvasToPdf.js +21 -5
  14. package/dist/export/pdf/types.d.ts +11 -0
  15. package/dist/exports.d.ts +2 -1
  16. package/dist/exports.js +2 -1
  17. package/dist/server.d.ts +3 -1
  18. package/dist/server.js +3 -1
  19. package/dist/types/pdfCanvas.d.ts +12 -1
  20. package/package.json +1 -1
  21. package/dist/canvas/AnnotationCanvas.d.ts +0 -11
  22. package/dist/canvas/AnnotationCanvas.js +0 -10
  23. package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
  24. package/dist/canvas/AnnotationCanvas.native.js +0 -6
  25. package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
  26. package/dist/canvas/AnnotationCanvasInner.js +0 -219
  27. package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
  28. package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
  29. package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
  30. package/dist/canvas/AnnotationCanvasSkia.js +0 -20
  31. package/dist/canvas/Tool.d.ts +0 -38
  32. package/dist/canvas/Tool.js +0 -1
  33. package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
  34. package/dist/canvas/elements/BackgroundImageElement.js +0 -37
  35. package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
  36. package/dist/canvas/elements/MeasurementStampElement.js +0 -30
  37. package/dist/canvas/elements/ShapeElement.d.ts +0 -7
  38. package/dist/canvas/elements/ShapeElement.js +0 -62
  39. package/dist/canvas/elements/StrokeElement.d.ts +0 -7
  40. package/dist/canvas/elements/StrokeElement.js +0 -18
  41. package/dist/canvas/measurementPicker.d.ts +0 -10
  42. package/dist/canvas/measurementPicker.js +0 -1
  43. package/dist/canvas/measurementStampOverlay.d.ts +0 -11
  44. package/dist/canvas/measurementStampOverlay.js +0 -1
  45. package/dist/canvas/pointerAdapter.d.ts +0 -3
  46. package/dist/canvas/pointerAdapter.js +0 -19
  47. package/dist/canvas/stampLayout.d.ts +0 -5
  48. package/dist/canvas/stampLayout.js +0 -14
  49. package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
  50. package/dist/canvas/tools/measurementStampTool.js +0 -37
  51. package/dist/canvas/tools/panTool.d.ts +0 -5
  52. package/dist/canvas/tools/panTool.js +0 -25
  53. package/dist/canvas/tools/penTool.d.ts +0 -13
  54. package/dist/canvas/tools/penTool.js +0 -68
  55. package/dist/canvas/tools/selectTool.d.ts +0 -2
  56. package/dist/canvas/tools/selectTool.js +0 -182
  57. package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
  58. package/dist/canvas/useAnnotationCanvasState.js +0 -210
  59. package/dist/canvas/viewport.d.ts +0 -16
  60. package/dist/canvas/viewport.js +0 -54
  61. package/dist/data/AnnotationDataContext.d.ts +0 -8
  62. package/dist/data/AnnotationDataContext.js +0 -11
  63. package/dist/data/AnnotationDataProvider.d.ts +0 -65
  64. package/dist/data/AnnotationDataProvider.js +0 -4
  65. package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
  66. package/dist/data/InMemoryAnnotationProvider.js +0 -197
  67. package/dist/data/canvasPersistence.d.ts +0 -3
  68. package/dist/data/canvasPersistence.js +0 -26
  69. package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
  70. package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
  71. package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
  72. package/dist/data/hooks/useAnnotationDoc.js +0 -33
  73. package/dist/data/hooks/useAnnotationList.d.ts +0 -7
  74. package/dist/data/hooks/useAnnotationList.js +0 -26
  75. package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
  76. package/dist/data/hooks/useAnnotationMutations.js +0 -11
  77. package/dist/hooks/useParseMeasurement.d.ts +0 -4
  78. package/dist/hooks/useParseMeasurement.js +0 -14
  79. package/dist/utils/evaluateFormula.d.ts +0 -20
  80. package/dist/utils/evaluateFormula.js +0 -31
@@ -9,8 +9,8 @@ import { DEFAULT_TEXT_FONT_SIZE, TEXT_BASELINE_FACTOR, TEXT_LINE_HEIGHT_FACTOR,
9
9
  import { normalizeRect, placementOf, } from '../../annotation/canvas/measurementGeometry.js';
10
10
  import { MEASUREMENT_LINE_COLOR, MEASUREMENT_LINE_WIDTH, MEASUREMENT_RECT_WIDTH, } from '../../annotation/canvas/measurementLineStyle.js';
11
11
  import { parseHexColor } from './color.js';
12
- import { circlePathOps, fillStateOps, flatPolylinePathOps, polylinePathOps, rectPathOps, strokeStateOps, } from './emit.js';
13
- import { boundsToRect } from './pageMapping.js';
12
+ import { circlePathOps, fillStateOps, flatPolylinePathOps, polylinePathOps, rectPathOps, strokeStateOps, withMatrix, } from './emit.js';
13
+ import { boundsToRect, } from './pageMapping.js';
14
14
  // Readable glyph colour over a highlight band — mirror of
15
15
  // ShapeElement.highlightTextColor (which lives behind a Skia import).
16
16
  const highlightTextColor = (hex) => {
@@ -118,10 +118,51 @@ export const strokeOps = (s, ctx) => {
118
118
  /* ------------------------------------------------------------------ *
119
119
  * Shapes
120
120
  * ------------------------------------------------------------------ */
121
+ /**
122
+ * Doc-space rotation of a text shape: `geometry.rotation` degrees CLOCKWISE
123
+ * (y-down) about its origin, `points[0]`. Only set by converted Nutrient text
124
+ * so far — the Skia canvas still draws every text shape upright.
125
+ */
126
+ const textRotationMatrix = (shape) => {
127
+ const deg = shape.geometry.rotation;
128
+ const origin = shape.geometry.points[0];
129
+ if (!deg || !origin || !Number.isFinite(deg) || deg % 360 === 0)
130
+ return null;
131
+ const t = (deg * Math.PI) / 180;
132
+ const c = Math.cos(t);
133
+ const s = Math.sin(t);
134
+ return [
135
+ c,
136
+ s,
137
+ -s,
138
+ c,
139
+ origin.x - c * origin.x + s * origin.y,
140
+ origin.y - s * origin.x - c * origin.y,
141
+ ];
142
+ };
121
143
  export const shapeBounds = (shape) => {
122
144
  if (shape.kind === 'text') {
123
145
  const b = textShapeBounds(shape);
124
- return b ? boundsToRect(b, 4) : null;
146
+ if (!b)
147
+ return null;
148
+ const m = textRotationMatrix(shape);
149
+ if (!m)
150
+ return boundsToRect(b, 4);
151
+ const corners = [
152
+ [b.minX, b.minY],
153
+ [b.maxX, b.minY],
154
+ [b.minX, b.maxY],
155
+ [b.maxX, b.maxY],
156
+ ].map(([x, y]) => ({
157
+ x: m[0] * x + m[2] * y + m[4],
158
+ y: m[1] * x + m[3] * y + m[5],
159
+ }));
160
+ return boundsToRect({
161
+ minX: Math.min(...corners.map((p) => p.x)),
162
+ minY: Math.min(...corners.map((p) => p.y)),
163
+ maxX: Math.max(...corners.map((p) => p.x)),
164
+ maxY: Math.max(...corners.map((p) => p.y)),
165
+ }, 4);
125
166
  }
126
167
  const b = visualShapeBounds(shape);
127
168
  if (!b)
@@ -169,7 +210,8 @@ const textShapeOps = (shape, ctx) => {
169
210
  reason: 'Some characters have no glyph in the export fonts and were dropped',
170
211
  });
171
212
  }
172
- return ops;
213
+ const rotation = textRotationMatrix(shape);
214
+ return rotation ? withMatrix(rotation, ops) : ops;
173
215
  };
174
216
  export const shapeOps = (shape, ctx) => {
175
217
  const { kind, geometry, style } = shape;
@@ -1,5 +1,14 @@
1
+ import type { PdfCanvasPage } from '../../types/pdfCanvas.js';
1
2
  import { type RenderCanvasToPdfInput, type RenderCanvasToPdfResult, type RenderPdfCanvasToPdfInput } from './types.js';
2
3
  export declare const renderCanvasToPdf: (input: RenderCanvasToPdfInput) => Promise<RenderCanvasToPdfResult>;
4
+ /**
5
+ * The `PdfCanvasDocument.pages` record for a PDF: each page's extents after
6
+ * its own /Rotate, measured exactly as renderPdfCanvasToPdf validates them,
7
+ * at user rotation 0. For building a PdfCanvas record server-side (e.g. when
8
+ * converting a legacy Nutrient file) without rasterizing. The PDF must be
9
+ * unencrypted (stripPdfEncryption).
10
+ */
11
+ export declare const describePdfPages: (sourcePdf: Uint8Array) => Promise<PdfCanvasPage[]>;
3
12
  /**
4
13
  * Export a PdfCanvas file. Each page's state is drawn in that page's own doc
5
14
  * space (the DISPLAYED page: rotated extents, origin top-left). A user
@@ -12,7 +12,7 @@
12
12
  // /Rotate and the page matrix.
13
13
  import { EncryptedPDFError, PDFDocument, degrees, } from 'pdf-lib';
14
14
  import { backgroundLayersOf } from '../../annotation/canvas/backgroundLayers.js';
15
- import { effectivePageRotation, pdfPageDisplaySize, } from '../../annotation/canvas/pdfCanvasPages.js';
15
+ import { effectivePageRotation, pdfCanvasPageKnobs, pdfPageDisplaySize, } from '../../annotation/canvas/pdfCanvasPages.js';
16
16
  import { fitRectToScreen } from '../../annotation/canvas/viewport.js';
17
17
  import { STAMP_TILE_SIZE } from '../../annotation/canvas/stampLayout.js';
18
18
  import { clipRectOps, withMatrix } from './emit.js';
@@ -174,7 +174,7 @@ const renderJobs = async (doc, jobs, options, skipped) => {
174
174
  const onPage = (b) => b !== null && rectsIntersect(b, rect);
175
175
  const hiddenLayerIds = new Set(job.layerDefs.filter((l) => l.visible === false).map((l) => l.id));
176
176
  const visible = (layerId) => !hiddenLayerIds.has(layerId);
177
- const footprintFor = (placed, content) => tileFootprint(placed, { tileDocSize: job.tileDocSize }, options.calibration, fallbackDocEdge, designBoxFor(placed, content));
177
+ const footprintFor = (placed, content) => tileFootprint(placed, { tileDocSize: job.tileDocSize }, job.calibration ?? options.calibration, fallbackDocEdge, designBoxFor(placed, content));
178
178
  const contentOf = (placed) => tileContent.get(placed.id) ?? { kind: 'empty' };
179
179
  if (options.mode === 'flatten') {
180
180
  // Everything into the page's content stream, under one matrix + clip.
@@ -334,6 +334,20 @@ export const renderCanvasToPdf = async (input) => {
334
334
  const pdf = await doc.save({ useObjectStreams: false });
335
335
  return { pdf, pageCount: pages.length, annotationCount, skipped };
336
336
  };
337
+ /**
338
+ * The `PdfCanvasDocument.pages` record for a PDF: each page's extents after
339
+ * its own /Rotate, measured exactly as renderPdfCanvasToPdf validates them,
340
+ * at user rotation 0. For building a PdfCanvas record server-side (e.g. when
341
+ * converting a legacy Nutrient file) without rasterizing. The PDF must be
342
+ * unencrypted (stripPdfEncryption).
343
+ */
344
+ export const describePdfPages = async (sourcePdf) => {
345
+ const doc = await loadSource(sourcePdf);
346
+ return doc.getPages().map((page) => {
347
+ const size = rotatedBoxSize(pageBox(page), normalizeRotation(page.getRotation().angle));
348
+ return { width: size.width, height: size.height, rotation: 0 };
349
+ });
350
+ };
337
351
  /**
338
352
  * Export a PdfCanvas file. Each page's state is drawn in that page's own doc
339
353
  * space (the DISPLAYED page: rotated extents, origin top-left). A user
@@ -363,18 +377,20 @@ export const renderPdfCanvasToPdf = async (input) => {
363
377
  box,
364
378
  rotation: effective,
365
379
  rect: { x: 0, y: 0, width: display.width, height: display.height },
366
- layers: [],
380
+ // The page's image layers (expanded image tiles), in page doc space.
381
+ layers: state?.backgroundImages ?? [],
367
382
  layerDefs: state?.layers ?? [],
368
383
  strokes: state?.strokes ?? [],
369
384
  shapes: state?.shapes ?? [],
370
385
  placed: state?.placedMeasurements ?? [],
371
- tileDocSize: pdfCanvas.tileDocSize,
386
+ tileDocSize: pdfCanvasPageKnobs(pdfCanvas, i).tileDocSize,
387
+ calibration: input.pageCalibrations?.[i],
372
388
  });
373
389
  });
374
390
  const skipped = [];
375
391
  const annotationCount = await renderJobs(doc, jobs, {
376
392
  resolveTile: input.resolveTile,
377
- layerImages: {},
393
+ layerImages: input.layerImages ?? {},
378
394
  calibration: input.calibration,
379
395
  primaryFont: input.primaryFont,
380
396
  mode: input.mode ?? 'annotations',
@@ -106,6 +106,17 @@ export interface RenderPdfCanvasToPdfInput {
106
106
  /** Page docs by page index; a page without one is exported un-annotated. */
107
107
  pageStates: Partial<Record<number, PdfCanvasPageState>>;
108
108
  resolveTile: (ref: PlacedMeasurementRef) => ResolvedTileContent | undefined;
109
+ /**
110
+ * Decoded bytes per image layer id, across every page's
111
+ * `backgroundImages`. Missing → that layer is skipped.
112
+ */
113
+ layerImages?: Record<string, EmbeddedImage>;
114
+ /**
115
+ * Per-page calibration (pdfCanvasPageTileDocSizes). Tile scale is per page,
116
+ * so a page's entry wins over `calibration`, which then only covers pages
117
+ * without one (and requests from clients that predate the split).
118
+ */
119
+ pageCalibrations?: Partial<Record<number, PdfExportCalibration>>;
109
120
  calibration?: PdfExportCalibration;
110
121
  primaryFont: Uint8Array;
111
122
  mode?: 'annotations' | 'flatten';
package/dist/exports.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from './types/firestore.js';
7
7
  export * from './types/layout.js';
8
8
  export * from './types/annotation.js';
9
9
  export * from './types/pdfCanvas.js';
10
+ export { computeBaseFitZoom, resolveTileFactors, tileDocSizesFor, pdfCanvasPageTileDocSizes, type ResolvedTileFactors, type TileDocSizes, } from './annotation/canvas/tileCalibration.js';
10
11
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
11
12
  export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
12
13
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
@@ -39,7 +40,7 @@ export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GRO
39
40
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, type GroupHeaderBadge, type GroupHeaderDescriptor, } from './annotation/canvas/groupHeaderLayout.js';
40
41
  export { createViewportApi, fitToScreen, fitRectToScreen, fitDocRectViewport, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, type ViewportApi, type ViewportState, type ZoomLimits, } from './annotation/canvas/viewport.js';
41
42
  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';
43
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, pdfCanvasPagePlacements, pdfCanvasPlacementsOf, pdfCanvasPageKnobs, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
43
44
  export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
44
45
  export { fileTileImageSource, expandFileTileToBackground, collapseBackgroundToFileTile, type FileTileImageSource, } from './annotation/canvas/fileTileSwap.js';
45
46
  export { createPenTool, type PenToolOptions, } from './annotation/canvas/tools/penTool.js';
package/dist/exports.js CHANGED
@@ -11,6 +11,7 @@ export * from './types/firestore.js';
11
11
  export * from './types/layout.js';
12
12
  export * from './types/annotation.js';
13
13
  export * from './types/pdfCanvas.js';
14
+ export { computeBaseFitZoom, resolveTileFactors, tileDocSizesFor, pdfCanvasPageTileDocSizes, } from './annotation/canvas/tileCalibration.js';
14
15
  export { getToleranceColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, } from './utils/tolerance.js';
15
16
  // Shared color palette (canonical source for both apps' brand colors).
16
17
  export { GeneralColors, SecondaryColors, FormColors, TileColors, FormulaColors, YellowColors, RedColors, GreenColors, tailwindColors, } from './theme/colors.js';
@@ -45,7 +46,7 @@ export { STAMP_TILE_SIZE, GROUP_HEADER_TILE_WIDTH, GROUP_HEADER_TILE_HEIGHT, GRO
45
46
  export { GROUP_HEADER_STYLE, describeGroupHeader, groupHeaderSignature, } from './annotation/canvas/groupHeaderLayout.js';
46
47
  export { createViewportApi, fitToScreen, fitRectToScreen, fitDocRectViewport, imageDocRect, panBy, zoomAt, DEFAULT_VIEWPORT, DEFAULT_ZOOM_LIMITS, } from './annotation/canvas/viewport.js';
47
48
  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';
49
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, emptyPdfCanvasPageState, pdfCanvasPageDocument, pdfCanvasKnobsOf, pdfCanvasKnobsEqual, pageStateToCanvasState, canvasStateToPageState, rotatePointInPage, rotatePageState, rotatePdfCanvasPage, pdfCanvasPagePlacements, pdfCanvasPlacementsOf, pdfCanvasPageKnobs, } from './annotation/canvas/pdfCanvasPages.js';
49
50
  export { LEGACY_BACKGROUND_LAYER_ID, BACKGROUND_PLACEMENT_GAP_RATIO, backgroundLayersOf, backgroundLayerIsLocked, isBackgroundLayerLocked, defaultBackgroundLayerLocked, backgroundLayerDocRect, backgroundLayersUnionRect, placeNewBackgroundLayer, } from './annotation/canvas/backgroundLayers.js';
50
51
  export { fileTileImageSource, expandFileTileToBackground, collapseBackgroundToFileTile, } from './annotation/canvas/fileTileSwap.js';
51
52
  export { createPenTool, } from './annotation/canvas/tools/penTool.js';
package/dist/server.d.ts CHANGED
@@ -6,7 +6,8 @@ export * from './types/firestore.js';
6
6
  export * from './types/layout.js';
7
7
  export * from './types/annotation.js';
8
8
  export * from './types/pdfCanvas.js';
9
- export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
9
+ export { computeBaseFitZoom, resolveTileFactors, tileDocSizesFor, pdfCanvasPageTileDocSizes, type ResolvedTileFactors, type TileDocSizes, } from './annotation/canvas/tileCalibration.js';
10
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, pdfCanvasPagePlacements, pdfCanvasPlacementsOf, pdfCanvasPageKnobs, type PdfPageDisplaySize, type PdfRotationDelta, } from './annotation/canvas/pdfCanvasPages.js';
10
11
  export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, type ToleranceThreshold, type ToleranceConfig, } from './utils/tolerance.js';
11
12
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
12
13
  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';
@@ -18,5 +19,6 @@ export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, }
18
19
  export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
19
20
  export * from './calculator/index.js';
20
21
  export * from './export/pdf/index.js';
22
+ export { instantJsonToPdfCanvas, NUTRIENT_TILE_DOC_SIZE, type InstantJsonConversion, type InstantJsonToPdfCanvasInput, type NutrientSkippedAnnotation, } from './export/nutrient/instantJsonToPdfCanvas.js';
21
23
  export { applyGroupFormulaUm, groupFormulaExpression, unitNumberToMicrometers, type GroupFormulaFrame, } from './formulas/groupFormula.js';
22
24
  export { MEASUREMENT_LINE_COLOR, MEASUREMENT_LINE_WIDTH, MEASUREMENT_RECT_WIDTH, } from './annotation/canvas/measurementLineStyle.js';
package/dist/server.js CHANGED
@@ -11,7 +11,8 @@ export * from './types/firestore.js';
11
11
  export * from './types/layout.js';
12
12
  export * from './types/annotation.js';
13
13
  export * from './types/pdfCanvas.js';
14
- export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, } from './annotation/canvas/pdfCanvasPages.js';
14
+ export { computeBaseFitZoom, resolveTileFactors, tileDocSizesFor, pdfCanvasPageTileDocSizes, } from './annotation/canvas/tileCalibration.js';
15
+ export { normalizePdfRotation, effectivePageRotation, pdfPageDisplaySize, pdfCanvasPageDocId, pageStateToCanvasState, rotatePageState, pdfCanvasPagePlacements, pdfCanvasPlacementsOf, pdfCanvasPageKnobs, } from './annotation/canvas/pdfCanvasPages.js';
15
16
  export { getToleranceColor, getToleranceSecondaryColor, calculateDeviationPercentage, isWithinTolerance, generateToleranceGradient, createDefaultToleranceThresholds, DEFAULT_TOLERANCE_COLORS, DEFAULT_TOLERANCE_SECONDARY_COLORS, } from './utils/tolerance.js';
16
17
  export { DEFAULT_GROUP_INDEX, isDefaultGroup, findDefaultGroup, isFormGroupCompleted, } from './utils/groups.js';
17
18
  export { ROLE_RANK, EMPTY_GRANTS, normalizeRole, roleAtLeast, effectiveRole, canView, canEdit, canManage, canManageOrg, isGuest, groupGrants, resolveAccess, } from './utils/permissions.js';
@@ -25,6 +26,7 @@ export * from './calculator/index.js';
25
26
  // Canvas → annotated PDF export (Cloud Function renderer). Pure pdf-lib; no
26
27
  // React or Skia anywhere in its import graph.
27
28
  export * from './export/pdf/index.js';
29
+ export { instantJsonToPdfCanvas, NUTRIENT_TILE_DOC_SIZE, } from './export/nutrient/instantJsonToPdfCanvas.js';
28
30
  // groupFormula's own micrometersToUnitNumber (integer constants, NaN on
29
31
  // unknown units) stays module-local: the canonical export of that name is the
30
32
  // mathjs-exact one from evaluateFormula.js above, and no consumer imported
@@ -1,4 +1,4 @@
1
- import type { AnnotationLayer, AnnotationShape, AnnotationStroke, PlacedMeasurementRef, PlaneCalibration } from './annotation.js';
1
+ import type { AnnotationBackgroundLayer, AnnotationLayer, AnnotationShape, AnnotationStroke, PlacedMeasurementRef, PlaneCalibration } from './annotation.js';
2
2
  export type PdfPageRotation = 0 | 90 | 180 | 270;
3
3
  export interface PdfCanvasPage {
4
4
  width: number;
@@ -23,8 +23,18 @@ export interface PdfCanvasData {
23
23
  headerTileScaleFactor?: number;
24
24
  headerTileScaleFactorMobile?: number;
25
25
  tileDocSize?: number;
26
+ pageTileScales?: Record<string, PdfCanvasTileScales>;
26
27
  annotatedPages: number[];
28
+ placementsByPage?: Record<string, PdfCanvasPlacement[]>;
27
29
  }
30
+ export interface PdfCanvasTileScales {
31
+ tileScaleFactor?: number;
32
+ tileScaleFactorMobile?: number;
33
+ headerTileScaleFactor?: number;
34
+ headerTileScaleFactorMobile?: number;
35
+ tileDocSize?: number;
36
+ }
37
+ export type PdfCanvasPlacement = Pick<PlacedMeasurementRef, 'measurementId' | 'measurementPath' | 'groupId' | 'columnId'>;
28
38
  export interface PdfCanvasPageState {
29
39
  schemaVersion: 1;
30
40
  pageIndex: number;
@@ -33,6 +43,7 @@ export interface PdfCanvasPageState {
33
43
  shapes: AnnotationShape[];
34
44
  placedMeasurements: PlacedMeasurementRef[];
35
45
  planes?: PlaneCalibration[];
46
+ backgroundImages?: AnnotationBackgroundLayer[];
36
47
  updatedAt: number;
37
48
  rev?: PdfCanvasRev;
38
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.26.1",
3
+ "version": "1.27.0",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -1,11 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.js';
3
- export type { AnnotationCanvasHandle, GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export interface CanvasKitOpts {
5
- locateFile?: (file: string) => string;
6
- }
7
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
8
- fallback?: ReactNode;
9
- canvasKitOpts?: CanvasKitOpts;
10
- };
11
- export declare const AnnotationCanvas: ({ fallback, canvasKitOpts, ...rest }: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,10 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { WithSkiaWeb } from '@shopify/react-native-skia/lib/module/web/index.js';
3
- // Web-only entry point: lazy-loads CanvasKit wasm via WithSkiaWeb. Imperative
4
- // API (undo/redo/zoom) is exposed through the `imperativeRef` prop rather
5
- // than React refs, since WithSkiaWeb can't forward refs through its lazy
6
- // component boundary. The native build sits in `AnnotationCanvas.native.tsx`
7
- // and skips WithSkiaWeb entirely (no wasm to load).
8
- export const AnnotationCanvas = ({ fallback, canvasKitOpts, ...rest }) => (_jsx(WithSkiaWeb, { getComponent: () => import('./AnnotationCanvasInner.js').then((m) => ({
9
- default: m.AnnotationCanvasInner,
10
- })), fallback: fallback ?? null, componentProps: rest, opts: canvasKitOpts }));
@@ -1,8 +0,0 @@
1
- import type { AnnotationCanvasInnerProps } from './AnnotationCanvasInner.native.js';
2
- export type { AnnotationCanvasHandle, } from './useAnnotationCanvasState.js';
3
- export type { GestureConfig, PanTrigger, } from './AnnotationCanvasInner.js';
4
- export type AnnotationCanvasProps = AnnotationCanvasInnerProps & {
5
- fallback?: unknown;
6
- canvasKitOpts?: unknown;
7
- };
8
- export declare const AnnotationCanvas: (props: AnnotationCanvasProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { AnnotationCanvasInner } from './AnnotationCanvasInner.native.js';
3
- export const AnnotationCanvas = (props) => {
4
- const { fallback: _f, canvasKitOpts: _o, ...rest } = props;
5
- return _jsx(AnnotationCanvasInner, { ...rest });
6
- };
@@ -1,39 +0,0 @@
1
- import { type CSSProperties, type MutableRefObject } from 'react';
2
- import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
3
- import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
4
- import type { MeasurementRef } from './measurementPicker.js';
5
- import type { Tool } from './Tool.js';
6
- import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
7
- import { type ViewportState } from './viewport.js';
8
- import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
9
- export type { AnnotationCanvasHandle };
10
- export interface AnnotationCanvasInnerProps {
11
- canvas: AnnotationCanvasState;
12
- onCommit(patch: AnnotationDocumentPatch): void;
13
- tools: Tool[];
14
- activeToolId: string;
15
- selection: Selection | null;
16
- onSelectionChange(selection: Selection | null): void;
17
- measurements?: Measurement[];
18
- fallbackUnit?: Units;
19
- fractionalTolerance?: FractionalTolerance;
20
- decimalTolerance?: DecimalTolerance;
21
- resolveImageUrl?: (storagePath: string) => Promise<string>;
22
- pickMeasurement?: () => Promise<MeasurementRef | null>;
23
- renderMeasurementStamp?: RenderMeasurementStamp;
24
- stampFontSource?: unknown;
25
- stampValueFontSize?: number;
26
- stampLabelFontSize?: number;
27
- gestures?: GestureConfig;
28
- width: number;
29
- height: number;
30
- initialViewport?: ViewportState;
31
- style?: CSSProperties;
32
- imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
33
- }
34
- export type PanTrigger = 'middleMouse' | 'rightMouse' | 'space';
35
- export interface GestureConfig {
36
- wheel?: 'zoom' | 'pan' | 'auto';
37
- panTriggers?: PanTrigger[];
38
- }
39
- export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,219 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useFont } from '@shopify/react-native-skia';
3
- import { useCallback, useEffect, useRef, } from 'react';
4
- import { AnnotationCanvasSkia } from './AnnotationCanvasSkia.js';
5
- import { useAnnotationCanvasState, } from './useAnnotationCanvasState.js';
6
- import { STAMP_TILE_SIZE } from './stampLayout.js';
7
- const DEFAULT_PAN_TRIGGERS = ['middleMouse', 'space'];
8
- export const AnnotationCanvasInner = (props) => {
9
- const { fallbackUnit, fractionalTolerance, decimalTolerance, resolveImageUrl, stampFontSource, stampValueFontSize = 14, stampLabelFontSize = 11, gestures, width, height, style, activeToolId, tools, } = props;
10
- const wheelMode = gestures?.wheel ?? 'auto';
11
- const panTriggers = gestures?.panTriggers ?? DEFAULT_PAN_TRIGGERS;
12
- const allowSpacePan = panTriggers.includes('space');
13
- const allowMiddlePan = panTriggers.includes('middleMouse');
14
- const allowRightPan = panTriggers.includes('rightMouse');
15
- const valueFont = useFont(stampFontSource, stampValueFontSize);
16
- const labelFont = useFont(stampFontSource, stampLabelFontSize);
17
- const state = useAnnotationCanvasState(props);
18
- const containerRef = useRef(null);
19
- const panGestureRef = useRef(null);
20
- const spaceDownRef = useRef(false);
21
- const activeTool = tools.find((t) => t.id === activeToolId) ?? null;
22
- const toCanvasPointer = useCallback((event) => {
23
- const rect = containerRef.current?.getBoundingClientRect();
24
- const screen = {
25
- x: event.clientX - (rect?.left ?? 0),
26
- y: event.clientY - (rect?.top ?? 0),
27
- };
28
- return {
29
- pointerId: event.pointerId,
30
- screen,
31
- world: state.ctx.viewport.screenToWorld(screen),
32
- pressure: event.pressure || undefined,
33
- shiftKey: event.shiftKey,
34
- altKey: event.altKey,
35
- metaKey: event.metaKey,
36
- ctrlKey: event.ctrlKey,
37
- };
38
- }, [state.ctx.viewport]);
39
- const isPanTriggerDown = useCallback((event) => {
40
- if (allowMiddlePan && event.button === 1)
41
- return true;
42
- if (allowRightPan && event.button === 2)
43
- return true;
44
- if (allowSpacePan && event.button === 0 && spaceDownRef.current)
45
- return true;
46
- return false;
47
- }, [allowMiddlePan, allowRightPan, allowSpacePan]);
48
- const handlePointerDown = useCallback((event) => {
49
- const rect = containerRef.current?.getBoundingClientRect();
50
- const screen = {
51
- x: event.clientX - (rect?.left ?? 0),
52
- y: event.clientY - (rect?.top ?? 0),
53
- };
54
- if (isPanTriggerDown(event)) {
55
- panGestureRef.current = { pointerId: event.pointerId, lastScreen: screen };
56
- event.currentTarget.setPointerCapture(event.pointerId);
57
- event.preventDefault();
58
- return;
59
- }
60
- event.currentTarget.setPointerCapture(event.pointerId);
61
- state.dispatchPointerDown(toCanvasPointer(event));
62
- }, [state, toCanvasPointer, isPanTriggerDown]);
63
- const handlePointerMove = useCallback((event) => {
64
- const pan = panGestureRef.current;
65
- if (pan && event.pointerId === pan.pointerId) {
66
- const rect = containerRef.current?.getBoundingClientRect();
67
- const screen = {
68
- x: event.clientX - (rect?.left ?? 0),
69
- y: event.clientY - (rect?.top ?? 0),
70
- };
71
- state.pan({
72
- x: screen.x - pan.lastScreen.x,
73
- y: screen.y - pan.lastScreen.y,
74
- });
75
- panGestureRef.current = { pointerId: pan.pointerId, lastScreen: screen };
76
- return;
77
- }
78
- state.dispatchPointerMove(toCanvasPointer(event));
79
- }, [state, toCanvasPointer]);
80
- const handlePointerUp = useCallback((event) => {
81
- const pan = panGestureRef.current;
82
- if (pan && event.pointerId === pan.pointerId) {
83
- panGestureRef.current = null;
84
- return;
85
- }
86
- state.dispatchPointerUp(toCanvasPointer(event));
87
- }, [state, toCanvasPointer]);
88
- const handlePointerCancel = useCallback(() => {
89
- panGestureRef.current = null;
90
- state.dispatchPointerCancel();
91
- }, [state]);
92
- const handleWheel = useCallback((event) => {
93
- const rect = containerRef.current?.getBoundingClientRect();
94
- const focal = {
95
- x: event.clientX - (rect?.left ?? 0),
96
- y: event.clientY - (rect?.top ?? 0),
97
- };
98
- if (event.ctrlKey || event.metaKey) {
99
- const factor = Math.exp(-event.deltaY / 200);
100
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
101
- return;
102
- }
103
- if (wheelMode === 'zoom') {
104
- const factor = Math.exp(-event.deltaY / 300);
105
- state.zoom(focal, state.ctx.viewport.state.zoom * factor);
106
- return;
107
- }
108
- if (event.shiftKey) {
109
- state.pan({ x: event.deltaY, y: event.deltaX });
110
- return;
111
- }
112
- state.pan({ x: event.deltaX, y: event.deltaY });
113
- }, [state, wheelMode]);
114
- const handleContextMenu = useCallback((event) => {
115
- if (allowRightPan)
116
- event.preventDefault();
117
- }, [allowRightPan]);
118
- useEffect(() => {
119
- const el = containerRef.current;
120
- if (!el)
121
- return;
122
- const stop = (e) => {
123
- if (e.target === el || el.contains(e.target)) {
124
- e.preventDefault();
125
- }
126
- };
127
- el.addEventListener('wheel', stop, { passive: false });
128
- return () => el.removeEventListener('wheel', stop);
129
- }, []);
130
- useEffect(() => {
131
- if (!allowSpacePan)
132
- return;
133
- const onKeyDown = (e) => {
134
- if (e.code === 'Space' && !e.repeat) {
135
- spaceDownRef.current = true;
136
- if (containerRef.current)
137
- containerRef.current.style.cursor = 'grab';
138
- }
139
- };
140
- const onKeyUp = (e) => {
141
- if (e.code === 'Space') {
142
- spaceDownRef.current = false;
143
- if (containerRef.current)
144
- containerRef.current.style.cursor = '';
145
- }
146
- };
147
- window.addEventListener('keydown', onKeyDown);
148
- window.addEventListener('keyup', onKeyUp);
149
- return () => {
150
- window.removeEventListener('keydown', onKeyDown);
151
- window.removeEventListener('keyup', onKeyUp);
152
- };
153
- }, [allowSpacePan]);
154
- const containerStyle = {
155
- position: 'relative',
156
- width,
157
- height,
158
- overflow: 'hidden',
159
- touchAction: 'none',
160
- userSelect: 'none',
161
- cursor: activeTool?.cursor ?? 'default',
162
- ...style,
163
- };
164
- const customPreview = activeTool?.renderPreview?.(state.customPreviewState, state.ctx);
165
- const { renderMeasurementStamp, selection } = props;
166
- return (_jsxs("div", { ref: containerRef, style: containerStyle, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerCancel, onWheel: handleWheel, onContextMenu: handleContextMenu, children: [AnnotationCanvasSkia({
167
- width,
168
- height,
169
- effectiveCanvas: state.effectiveCanvas,
170
- worldTransform: state.worldTransform,
171
- measurementsById: state.measurementsById,
172
- fallbackUnit,
173
- fractionalTolerance,
174
- decimalTolerance,
175
- resolveImageUrl,
176
- valueFont,
177
- labelFont,
178
- hideMeasurementStamps: !!renderMeasurementStamp,
179
- penDrawingStroke: state.penDrawingStroke,
180
- customPreview,
181
- }), renderMeasurementStamp && (_jsx("div", { style: {
182
- position: 'absolute',
183
- inset: 0,
184
- pointerEvents: 'none',
185
- }, children: state.effectiveCanvas.placedMeasurements.map((placed) => {
186
- const size = STAMP_TILE_SIZE * (placed.scale ?? 1);
187
- const cx = (placed.anchor.x - state.viewport.pan.x) * state.viewport.zoom;
188
- const cy = (placed.anchor.y - state.viewport.pan.y) * state.viewport.zoom;
189
- const isSelected = selection?.ids.includes(placed.id) ?? false;
190
- return (_jsxs("div", { style: {
191
- position: 'absolute',
192
- left: 0,
193
- top: 0,
194
- width: size,
195
- height: size,
196
- transform: `translate(${cx - size / 2}px, ${cy - size / 2}px)`,
197
- }, children: [renderMeasurementStamp({
198
- placed,
199
- measurement: state.measurementsById.get(placed.measurementId) ?? null,
200
- selected: isSelected,
201
- size,
202
- zoom: state.viewport.zoom,
203
- }), isSelected && (_jsx("div", { role: "button", "aria-label": "Remove measurement", onPointerDown: (e) => {
204
- e.stopPropagation();
205
- state.ctx.commit({
206
- ops: [{ op: 'removeMeasurement', id: placed.id }],
207
- });
208
- state.ctx.setSelection(null);
209
- }, style: {
210
- position: 'absolute',
211
- top: -10,
212
- right: -10,
213
- width: 40,
214
- height: 40,
215
- cursor: 'pointer',
216
- pointerEvents: 'auto',
217
- } }))] }, placed.id));
218
- }) }))] }));
219
- };
@@ -1,35 +0,0 @@
1
- import { type MutableRefObject } from 'react';
2
- import { type ViewStyle } from 'react-native';
3
- import type { RenderMeasurementStamp } from './measurementStampOverlay.js';
4
- import type { DecimalTolerance, FractionalTolerance, Measurement, Units } from '../types/firestore.js';
5
- import type { AnnotationCanvasState, AnnotationDocumentPatch, Selection } from '../types/annotation.js';
6
- import type { MeasurementRef } from './measurementPicker.js';
7
- import type { Tool } from './Tool.js';
8
- import { type AnnotationCanvasHandle } from './useAnnotationCanvasState.js';
9
- import type { ViewportState } from './viewport.js';
10
- export type { AnnotationCanvasHandle };
11
- export interface AnnotationCanvasInnerProps {
12
- canvas: AnnotationCanvasState;
13
- onCommit(patch: AnnotationDocumentPatch): void;
14
- tools: Tool[];
15
- activeToolId: string;
16
- selection: Selection | null;
17
- onSelectionChange(selection: Selection | null): void;
18
- measurements?: Measurement[];
19
- fallbackUnit?: Units;
20
- fractionalTolerance?: FractionalTolerance;
21
- decimalTolerance?: DecimalTolerance;
22
- resolveImageUrl?: (storagePath: string) => Promise<string>;
23
- pickMeasurement?: () => Promise<MeasurementRef | null>;
24
- renderMeasurementStamp?: RenderMeasurementStamp;
25
- stampFontSource?: unknown;
26
- stampValueFontSize?: number;
27
- stampLabelFontSize?: number;
28
- gestures?: unknown;
29
- width: number;
30
- height: number;
31
- initialViewport?: ViewportState;
32
- style?: ViewStyle;
33
- imperativeRef?: MutableRefObject<AnnotationCanvasHandle | null>;
34
- }
35
- export declare const AnnotationCanvasInner: (props: AnnotationCanvasInnerProps) => import("react/jsx-runtime").JSX.Element;