@reekon-tools/boldr-utils 1.21.1 → 1.22.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 (98) hide show
  1. package/dist/annotation/canvas/AnnotationCanvasSkia.js +3 -11
  2. package/dist/annotation/canvas/measurementLineStyle.d.ts +3 -0
  3. package/dist/annotation/canvas/measurementLineStyle.js +14 -0
  4. package/dist/export/pdf/color.d.ts +7 -0
  5. package/dist/export/pdf/color.js +39 -0
  6. package/dist/export/pdf/emit.d.ts +37 -0
  7. package/dist/export/pdf/emit.js +129 -0
  8. package/dist/export/pdf/fonts.d.ts +38 -0
  9. package/dist/export/pdf/fonts.js +97 -0
  10. package/dist/export/pdf/index.d.ts +5 -0
  11. package/dist/export/pdf/index.js +5 -0
  12. package/dist/export/pdf/layers.d.ts +5 -0
  13. package/dist/export/pdf/layers.js +19 -0
  14. package/dist/export/pdf/marks.d.ts +20 -0
  15. package/dist/export/pdf/marks.js +336 -0
  16. package/dist/export/pdf/pageMapping.d.ts +49 -0
  17. package/dist/export/pdf/pageMapping.js +67 -0
  18. package/dist/export/pdf/renderCanvasToPdf.d.ts +2 -0
  19. package/dist/export/pdf/renderCanvasToPdf.js +201 -0
  20. package/dist/export/pdf/tileContent.d.ts +39 -0
  21. package/dist/export/pdf/tileContent.js +218 -0
  22. package/dist/export/pdf/tiles.d.ts +43 -0
  23. package/dist/export/pdf/tiles.js +392 -0
  24. package/dist/export/pdf/types.d.ts +101 -0
  25. package/dist/export/pdf/types.js +10 -0
  26. package/dist/exports.d.ts +1 -1
  27. package/dist/exports.js +1 -1
  28. package/dist/formulas/calculateFormula.d.ts +1 -1
  29. package/dist/formulas/calculateFormula.js +23 -10
  30. package/dist/formulas/evaluateFormula.d.ts +0 -10
  31. package/dist/formulas/evaluateFormula.js +1 -12
  32. package/dist/formulas/groupFormula.d.ts +43 -0
  33. package/dist/formulas/groupFormula.js +168 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/server.d.ts +4 -1
  37. package/dist/server.js +6 -1
  38. package/package.json +4 -1
  39. package/dist/canvas/AnnotationCanvas.d.ts +0 -11
  40. package/dist/canvas/AnnotationCanvas.js +0 -10
  41. package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
  42. package/dist/canvas/AnnotationCanvas.native.js +0 -6
  43. package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
  44. package/dist/canvas/AnnotationCanvasInner.js +0 -219
  45. package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
  46. package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
  47. package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
  48. package/dist/canvas/AnnotationCanvasSkia.js +0 -20
  49. package/dist/canvas/Tool.d.ts +0 -38
  50. package/dist/canvas/Tool.js +0 -1
  51. package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
  52. package/dist/canvas/elements/BackgroundImageElement.js +0 -37
  53. package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
  54. package/dist/canvas/elements/MeasurementStampElement.js +0 -30
  55. package/dist/canvas/elements/ShapeElement.d.ts +0 -7
  56. package/dist/canvas/elements/ShapeElement.js +0 -62
  57. package/dist/canvas/elements/StrokeElement.d.ts +0 -7
  58. package/dist/canvas/elements/StrokeElement.js +0 -18
  59. package/dist/canvas/measurementPicker.d.ts +0 -10
  60. package/dist/canvas/measurementPicker.js +0 -1
  61. package/dist/canvas/measurementStampOverlay.d.ts +0 -11
  62. package/dist/canvas/measurementStampOverlay.js +0 -1
  63. package/dist/canvas/pointerAdapter.d.ts +0 -3
  64. package/dist/canvas/pointerAdapter.js +0 -19
  65. package/dist/canvas/stampLayout.d.ts +0 -5
  66. package/dist/canvas/stampLayout.js +0 -14
  67. package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
  68. package/dist/canvas/tools/measurementStampTool.js +0 -37
  69. package/dist/canvas/tools/panTool.d.ts +0 -5
  70. package/dist/canvas/tools/panTool.js +0 -25
  71. package/dist/canvas/tools/penTool.d.ts +0 -13
  72. package/dist/canvas/tools/penTool.js +0 -68
  73. package/dist/canvas/tools/selectTool.d.ts +0 -2
  74. package/dist/canvas/tools/selectTool.js +0 -182
  75. package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
  76. package/dist/canvas/useAnnotationCanvasState.js +0 -210
  77. package/dist/canvas/viewport.d.ts +0 -16
  78. package/dist/canvas/viewport.js +0 -54
  79. package/dist/data/AnnotationDataContext.d.ts +0 -8
  80. package/dist/data/AnnotationDataContext.js +0 -11
  81. package/dist/data/AnnotationDataProvider.d.ts +0 -65
  82. package/dist/data/AnnotationDataProvider.js +0 -4
  83. package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
  84. package/dist/data/InMemoryAnnotationProvider.js +0 -197
  85. package/dist/data/canvasPersistence.d.ts +0 -3
  86. package/dist/data/canvasPersistence.js +0 -26
  87. package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
  88. package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
  89. package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
  90. package/dist/data/hooks/useAnnotationDoc.js +0 -33
  91. package/dist/data/hooks/useAnnotationList.d.ts +0 -7
  92. package/dist/data/hooks/useAnnotationList.js +0 -26
  93. package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
  94. package/dist/data/hooks/useAnnotationMutations.js +0 -11
  95. package/dist/hooks/useParseMeasurement.d.ts +0 -4
  96. package/dist/hooks/useParseMeasurement.js +0 -14
  97. package/dist/utils/evaluateFormula.d.ts +0 -20
  98. package/dist/utils/evaluateFormula.js +0 -31
@@ -0,0 +1,168 @@
1
+ // A LIST group's batch formula, applied to one measurement value — the
2
+ // evaluation every tile that shows a group-formula'd measurement performs.
3
+ //
4
+ // Ported from the two apps' `lib/calculator.ts` (`applyFormulaToMeasurement` +
5
+ // `arrayToExpression`), which are byte-for-byte duplicates of each other, so
6
+ // the server-side PDF export shows the same value the screens do. Kept
7
+ // deliberately narrow: only the frame kinds a GROUP formula can hold. The
8
+ // calculator's inspection-input frames are not group-formula content and are
9
+ // emitted as an empty token here, exactly as the apps' `default:` branch does.
10
+ import { create, all } from 'mathjs';
11
+ import { Units } from '../types/firestore.js';
12
+ const math = create(all);
13
+ const MICROMETERS_PER_IN = 25400;
14
+ const MICROMETERS_PER_FT = 304800;
15
+ const MICROMETERS_PER_MM = 1000;
16
+ const MICROMETERS_PER_CM = 10000;
17
+ const MICROMETERS_PER_M = 1000000;
18
+ /** Frame types of the stack-expression formula the apps persist. */
19
+ export var GroupFormulaFrameType;
20
+ (function (GroupFormulaFrameType) {
21
+ GroupFormulaFrameType["Operator"] = "operator";
22
+ GroupFormulaFrameType["Number"] = "number";
23
+ GroupFormulaFrameType["Paren"] = "paren";
24
+ GroupFormulaFrameType["Fraction"] = "fraction";
25
+ GroupFormulaFrameType["FeetInches"] = "feetInches";
26
+ GroupFormulaFrameType["MeasurementObj"] = "measurement";
27
+ GroupFormulaFrameType["InspectionInput"] = "InspectionInput";
28
+ GroupFormulaFrameType["Func"] = "func";
29
+ GroupFormulaFrameType["Output"] = "output";
30
+ })(GroupFormulaFrameType || (GroupFormulaFrameType = {}));
31
+ const toNumber = (v) => {
32
+ const n = parseInt(v, 10);
33
+ return Number.isNaN(n) ? 0 : n;
34
+ };
35
+ const fractionToDecimal = (f) => {
36
+ const numerator = parseInt(f.numerator, 10);
37
+ const denominator = parseInt(f.denominator, 10);
38
+ let frac = 0;
39
+ if (!(Number.isNaN(numerator) && Number.isNaN(denominator))) {
40
+ const den = Number.isNaN(denominator) ? 0 : denominator;
41
+ frac =
42
+ den === 0
43
+ ? 0
44
+ : math.number(math.fraction(Number.isNaN(numerator) ? 0 : numerator, den));
45
+ }
46
+ return frac + toNumber(f.int);
47
+ };
48
+ const feetInchesToDecimalFeet = (v) => {
49
+ const feet = parseInt(v.feet, 10);
50
+ const inchInt = parseInt(v.int, 10);
51
+ const numerator = parseInt(v.numerator, 10);
52
+ const denominator = parseInt(v.denominator, 10);
53
+ const inchFrac = !Number.isNaN(numerator) && !Number.isNaN(denominator) && denominator > 0
54
+ ? math.number(math.fraction(numerator, denominator))
55
+ : 0;
56
+ const inches = (Number.isNaN(inchInt) ? 0 : inchInt) + inchFrac;
57
+ const sign = v.feet.trim().startsWith('-') ? -1 : 1;
58
+ return (Number.isNaN(feet) ? 0 : feet) + (sign * inches) / 12;
59
+ };
60
+ /** Composite feet-inches units evaluate in decimal feet; everything else as-is. */
61
+ const evaluationUnit = (u) => {
62
+ if (!u)
63
+ return Units.Inches;
64
+ return u === Units.FeetInchesDecimal || u === Units.FeetInchesFractional
65
+ ? Units.Feet
66
+ : u;
67
+ };
68
+ export const micrometersToUnitNumber = (um, unit) => {
69
+ switch (unit) {
70
+ case Units.Millimeters:
71
+ return um / MICROMETERS_PER_MM;
72
+ case Units.Centimeters:
73
+ return um / MICROMETERS_PER_CM;
74
+ case Units.Meters:
75
+ return um / MICROMETERS_PER_M;
76
+ case Units.Feet:
77
+ case Units.FeetInchesDecimal:
78
+ case Units.FeetInchesFractional:
79
+ return um / MICROMETERS_PER_FT;
80
+ case Units.Inches:
81
+ case Units.FractionalInches:
82
+ return um / MICROMETERS_PER_IN;
83
+ default:
84
+ return NaN;
85
+ }
86
+ };
87
+ export const unitNumberToMicrometers = (value, unit) => {
88
+ switch (unit) {
89
+ case Units.Millimeters:
90
+ return value * MICROMETERS_PER_MM;
91
+ case Units.Centimeters:
92
+ return value * MICROMETERS_PER_CM;
93
+ case Units.Meters:
94
+ return value * MICROMETERS_PER_M;
95
+ case Units.Feet:
96
+ case Units.FeetInchesDecimal:
97
+ case Units.FeetInchesFractional:
98
+ return value * MICROMETERS_PER_FT;
99
+ case Units.Inches:
100
+ case Units.FractionalInches:
101
+ return value * MICROMETERS_PER_IN;
102
+ default:
103
+ return NaN;
104
+ }
105
+ };
106
+ const findLastIndex = (arr, pred) => {
107
+ for (let i = arr.length - 1; i >= 0; i--)
108
+ if (pred(arr[i]))
109
+ return i;
110
+ return -1;
111
+ };
112
+ /** The mathjs expression string a stack formula evaluates to, for `valueUm`. */
113
+ export const groupFormulaExpression = (frames, valueUm, unit) => {
114
+ const outputIndex = findLastIndex(frames, (f) => f.type === GroupFormulaFrameType.Output);
115
+ const sliced = outputIndex === -1 ? frames : frames.slice(outputIndex);
116
+ const evalUnit = evaluationUnit(unit);
117
+ return sliced
118
+ .map((item) => {
119
+ switch (item.type) {
120
+ case GroupFormulaFrameType.Fraction:
121
+ return String(fractionToDecimal(item.value));
122
+ case GroupFormulaFrameType.FeetInches:
123
+ return String(feetInchesToDecimalFeet(item.value));
124
+ case GroupFormulaFrameType.Number:
125
+ case GroupFormulaFrameType.Operator:
126
+ case GroupFormulaFrameType.Paren:
127
+ case GroupFormulaFrameType.Output:
128
+ return String(item.value ?? '');
129
+ case GroupFormulaFrameType.MeasurementObj:
130
+ // The measurement frame stands for THE measurement being displayed.
131
+ return String(micrometersToUnitNumber(valueUm, evalUnit));
132
+ case GroupFormulaFrameType.Func: {
133
+ const funcValue = String(item.value ?? '');
134
+ const m = item.measurement;
135
+ if (m && typeof m.value === 'number') {
136
+ const converted = micrometersToUnitNumber(m.value, evalUnit);
137
+ return funcValue.replace(m.value.toString(), converted.toString());
138
+ }
139
+ return funcValue;
140
+ }
141
+ default:
142
+ return '';
143
+ }
144
+ })
145
+ .join(' ');
146
+ };
147
+ /**
148
+ * Apply a group's stored `formula` to a measurement value (micrometers) and
149
+ * return the result in micrometers, or null when the group has no formula or
150
+ * the expression does not evaluate to a finite number (the caller falls back
151
+ * to the raw value, exactly like the tiles do).
152
+ */
153
+ export const applyGroupFormulaUm = (formula, valueUm, unit) => {
154
+ if (!Array.isArray(formula) || formula.length === 0)
155
+ return null;
156
+ const expression = groupFormulaExpression(formula, valueUm, unit);
157
+ let result;
158
+ try {
159
+ result = math.evaluate(expression);
160
+ }
161
+ catch {
162
+ return null;
163
+ }
164
+ if (typeof result !== 'number' || !Number.isFinite(result))
165
+ return null;
166
+ const um = unitNumberToMicrometers(result, evaluationUnit(unit));
167
+ return Number.isFinite(um) ? um : null;
168
+ };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './exports.js';
2
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, micrometersToUnitNumber, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
3
3
  export { calculateFormula } from './formulas/calculateFormula.js';
4
4
  export { convertMicrometers } from './utils/micrometersToUnit.js';
5
5
  export { parseMeasurement } from './utils/parseMeasurement.js';
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // WithSkiaWeb. Metro/RN consumers resolve `index.native.ts` instead via
4
4
  // the `react-native` condition in package.json `exports`.
5
5
  export * from './exports.js';
6
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, micrometersToUnitNumber, } from './formulas/evaluateFormula.js';
6
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
7
7
  export { calculateFormula } from './formulas/calculateFormula.js';
8
8
  export { convertMicrometers } from './utils/micrometersToUnit.js';
9
9
  export { parseMeasurement } from './utils/parseMeasurement.js';
package/dist/server.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, micrometersToUnitNumber, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
1
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, type EnhancedMapping, type FormulaDefinition, type FormulaEvaluationOptions, } from './formulas/evaluateFormula.js';
2
2
  export { calculateFormula } from './formulas/calculateFormula.js';
3
3
  export { convertMicrometers } from './utils/micrometersToUnit.js';
4
4
  export { parseMeasurement } from './utils/parseMeasurement.js';
@@ -15,3 +15,6 @@ export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, type DesktopLin
15
15
  export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
16
16
  export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
17
17
  export * from './calculator/index.js';
18
+ export * from './export/pdf/index.js';
19
+ export { applyGroupFormulaUm, groupFormulaExpression, micrometersToUnitNumber, unitNumberToMicrometers, type GroupFormulaFrame, } from './formulas/groupFormula.js';
20
+ export { MEASUREMENT_LINE_COLOR, MEASUREMENT_LINE_WIDTH, MEASUREMENT_RECT_WIDTH, } from './annotation/canvas/measurementLineStyle.js';
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // root barrel, whose module graph statically pulls in `react` and
4
4
  // `@shopify/react-native-skia`. Only modules with no React/Skia anywhere
5
5
  // in their import graph may be re-exported from this file.
6
- export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, micrometersToUnitNumber, } from './formulas/evaluateFormula.js';
6
+ export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
7
7
  export { calculateFormula } from './formulas/calculateFormula.js';
8
8
  export { convertMicrometers } from './utils/micrometersToUnit.js';
9
9
  export { parseMeasurement } from './utils/parseMeasurement.js';
@@ -20,3 +20,8 @@ export { desktopBaseUrlForFirebaseProject, desktopFileEditorUrl, } from './expor
20
20
  export { numberToLetterIndex, formGroupInputLabel, listGroupMeasurementLabel, } from './utils/indexLabels.js';
21
21
  export { toSelectedValues, formatSelectedValues, toStoredValue, } from './utils/selectValues.js';
22
22
  export * from './calculator/index.js';
23
+ // Canvas → annotated PDF export (Cloud Function renderer). Pure pdf-lib; no
24
+ // React or Skia anywhere in its import graph.
25
+ export * from './export/pdf/index.js';
26
+ export { applyGroupFormulaUm, groupFormulaExpression, micrometersToUnitNumber, unitNumberToMicrometers, } from './formulas/groupFormula.js';
27
+ export { MEASUREMENT_LINE_COLOR, MEASUREMENT_LINE_WIDTH, MEASUREMENT_RECT_WIDTH, } from './annotation/canvas/measurementLineStyle.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reekon-tools/boldr-utils",
3
- "version": "1.21.1",
3
+ "version": "1.22.0",
4
4
  "description": "Shared utilities for formulas and measurement conversion used in Reekon apps",
5
5
  "author": "REEKON Tools",
6
6
  "license": "MIT",
@@ -47,7 +47,9 @@
47
47
  "keywords": [],
48
48
  "repository": "https://gitlab.com/reekon-tools/software/rock-pro/boldr-utils#",
49
49
  "dependencies": {
50
+ "@pdf-lib/fontkit": "1.1.1",
50
51
  "mathjs": "^11.11.0",
52
+ "pdf-lib": "1.17.1",
51
53
  "zod": "^4.4.3"
52
54
  },
53
55
  "peerDependencies": {
@@ -70,6 +72,7 @@
70
72
  },
71
73
  "devDependencies": {
72
74
  "@arethetypeswrong/cli": "^0.18.1",
75
+ "@embedpdf/pdfium": "2.15.0",
73
76
  "@shopify/react-native-skia": "^2.6.2",
74
77
  "@types/node": "^22.15.18",
75
78
  "@types/react": "^19.0.8",
@@ -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;