@reekon-tools/boldr-utils 1.21.1 → 1.23.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.
- package/dist/annotation/canvas/AnnotationCanvasSkia.js +3 -11
- package/dist/annotation/canvas/measurementLineStyle.d.ts +3 -0
- package/dist/annotation/canvas/measurementLineStyle.js +14 -0
- package/dist/export/pdf/annots.d.ts +23 -0
- package/dist/export/pdf/annots.js +135 -0
- package/dist/export/pdf/color.d.ts +7 -0
- package/dist/export/pdf/color.js +39 -0
- package/dist/export/pdf/decrypt.d.ts +21 -0
- package/dist/export/pdf/decrypt.js +121 -0
- package/dist/export/pdf/emit.d.ts +37 -0
- package/dist/export/pdf/emit.js +129 -0
- package/dist/export/pdf/fonts.d.ts +37 -0
- package/dist/export/pdf/fonts.js +85 -0
- package/dist/export/pdf/index.d.ts +7 -0
- package/dist/export/pdf/index.js +7 -0
- package/dist/export/pdf/layers.d.ts +5 -0
- package/dist/export/pdf/layers.js +19 -0
- package/dist/export/pdf/marks.d.ts +81 -0
- package/dist/export/pdf/marks.js +479 -0
- package/dist/export/pdf/pageMapping.d.ts +51 -0
- package/dist/export/pdf/pageMapping.js +77 -0
- package/dist/export/pdf/renderCanvasToPdf.d.ts +2 -0
- package/dist/export/pdf/renderCanvasToPdf.js +288 -0
- package/dist/export/pdf/resources.d.ts +35 -0
- package/dist/export/pdf/resources.js +95 -0
- package/dist/export/pdf/tileContent.d.ts +39 -0
- package/dist/export/pdf/tileContent.js +218 -0
- package/dist/export/pdf/tiles.d.ts +39 -0
- package/dist/export/pdf/tiles.js +392 -0
- package/dist/export/pdf/types.d.ts +112 -0
- package/dist/export/pdf/types.js +10 -0
- package/dist/exports.d.ts +1 -1
- package/dist/exports.js +1 -1
- package/dist/formulas/calculateFormula.d.ts +1 -1
- package/dist/formulas/calculateFormula.js +23 -10
- package/dist/formulas/evaluateFormula.d.ts +0 -10
- package/dist/formulas/evaluateFormula.js +1 -12
- package/dist/formulas/groupFormula.d.ts +43 -0
- package/dist/formulas/groupFormula.js +168 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +6 -1
- package/package.json +9 -1
- package/dist/canvas/AnnotationCanvas.d.ts +0 -11
- package/dist/canvas/AnnotationCanvas.js +0 -10
- package/dist/canvas/AnnotationCanvas.native.d.ts +0 -8
- package/dist/canvas/AnnotationCanvas.native.js +0 -6
- package/dist/canvas/AnnotationCanvasInner.d.ts +0 -39
- package/dist/canvas/AnnotationCanvasInner.js +0 -219
- package/dist/canvas/AnnotationCanvasInner.native.d.ts +0 -35
- package/dist/canvas/AnnotationCanvasInner.native.js +0 -138
- package/dist/canvas/AnnotationCanvasSkia.d.ts +0 -27
- package/dist/canvas/AnnotationCanvasSkia.js +0 -20
- package/dist/canvas/Tool.d.ts +0 -38
- package/dist/canvas/Tool.js +0 -1
- package/dist/canvas/elements/BackgroundImageElement.d.ts +0 -9
- package/dist/canvas/elements/BackgroundImageElement.js +0 -37
- package/dist/canvas/elements/MeasurementStampElement.d.ts +0 -13
- package/dist/canvas/elements/MeasurementStampElement.js +0 -30
- package/dist/canvas/elements/ShapeElement.d.ts +0 -7
- package/dist/canvas/elements/ShapeElement.js +0 -62
- package/dist/canvas/elements/StrokeElement.d.ts +0 -7
- package/dist/canvas/elements/StrokeElement.js +0 -18
- package/dist/canvas/measurementPicker.d.ts +0 -10
- package/dist/canvas/measurementPicker.js +0 -1
- package/dist/canvas/measurementStampOverlay.d.ts +0 -11
- package/dist/canvas/measurementStampOverlay.js +0 -1
- package/dist/canvas/pointerAdapter.d.ts +0 -3
- package/dist/canvas/pointerAdapter.js +0 -19
- package/dist/canvas/stampLayout.d.ts +0 -5
- package/dist/canvas/stampLayout.js +0 -14
- package/dist/canvas/tools/measurementStampTool.d.ts +0 -9
- package/dist/canvas/tools/measurementStampTool.js +0 -37
- package/dist/canvas/tools/panTool.d.ts +0 -5
- package/dist/canvas/tools/panTool.js +0 -25
- package/dist/canvas/tools/penTool.d.ts +0 -13
- package/dist/canvas/tools/penTool.js +0 -68
- package/dist/canvas/tools/selectTool.d.ts +0 -2
- package/dist/canvas/tools/selectTool.js +0 -182
- package/dist/canvas/useAnnotationCanvasState.d.ts +0 -54
- package/dist/canvas/useAnnotationCanvasState.js +0 -210
- package/dist/canvas/viewport.d.ts +0 -16
- package/dist/canvas/viewport.js +0 -54
- package/dist/data/AnnotationDataContext.d.ts +0 -8
- package/dist/data/AnnotationDataContext.js +0 -11
- package/dist/data/AnnotationDataProvider.d.ts +0 -65
- package/dist/data/AnnotationDataProvider.js +0 -4
- package/dist/data/InMemoryAnnotationProvider.d.ts +0 -30
- package/dist/data/InMemoryAnnotationProvider.js +0 -197
- package/dist/data/canvasPersistence.d.ts +0 -3
- package/dist/data/canvasPersistence.js +0 -26
- package/dist/data/hooks/useAnnotationCanvasDoc.d.ts +0 -33
- package/dist/data/hooks/useAnnotationCanvasDoc.js +0 -314
- package/dist/data/hooks/useAnnotationDoc.d.ts +0 -7
- package/dist/data/hooks/useAnnotationDoc.js +0 -33
- package/dist/data/hooks/useAnnotationList.d.ts +0 -7
- package/dist/data/hooks/useAnnotationList.js +0 -26
- package/dist/data/hooks/useAnnotationMutations.d.ts +0 -9
- package/dist/data/hooks/useAnnotationMutations.js +0 -11
- package/dist/hooks/useParseMeasurement.d.ts +0 -4
- package/dist/hooks/useParseMeasurement.js +0 -14
- package/dist/utils/evaluateFormula.d.ts +0 -20
- package/dist/utils/evaluateFormula.js +0 -31
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { GroupHeaderDescriptor } from '../../annotation/canvas/groupHeaderLayout.js';
|
|
2
|
+
import type { AnnotationCanvasState, PlacedMeasurementRef } from '../../types/annotation.js';
|
|
3
|
+
export interface EmbeddedImage {
|
|
4
|
+
bytes: Uint8Array;
|
|
5
|
+
mime: 'image/png' | 'image/jpeg';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Doc-space edge length (PDF points) a scale-1 measurement tile covers, and
|
|
9
|
+
* the same for header chips and object tiles. Tile footprints on screen are
|
|
10
|
+
* zoom-relative, so only the client that is exporting knows how large its
|
|
11
|
+
* tiles are against the drawing; it computes these with
|
|
12
|
+
* `tileDocSizeForFactor(resolvedFactor, baseFitZoom)` (stampLayout.ts) and
|
|
13
|
+
* sends them along. Absent fields fall back to the persisted `tileDocSize`,
|
|
14
|
+
* then to a default derived from the first page.
|
|
15
|
+
*/
|
|
16
|
+
export interface PdfExportCalibration {
|
|
17
|
+
tileDocSize?: number;
|
|
18
|
+
headerTileDocSize?: number;
|
|
19
|
+
fileTileDocSize?: number;
|
|
20
|
+
}
|
|
21
|
+
/** What a measurement / angle / formula tile displays. */
|
|
22
|
+
export interface MeasurementTileContent {
|
|
23
|
+
kind: 'measurement';
|
|
24
|
+
/** Converted display string, e.g. "12 3/16", "5' 6 1/2\"", "45.2°". */
|
|
25
|
+
value: string;
|
|
26
|
+
/** Unit column text ("in", "mm", "ft"); empty for scalars. */
|
|
27
|
+
unit: string;
|
|
28
|
+
/** Header-row identifier ("A3", "B12"); empty hides it. */
|
|
29
|
+
positionLabel?: string;
|
|
30
|
+
/** Bottom label (measurement label / column name / override). */
|
|
31
|
+
label?: string;
|
|
32
|
+
/** Card fill (measurementTileFill precedence already applied). */
|
|
33
|
+
fill: string;
|
|
34
|
+
/** Formula tiles carry their label on a dark-magenta bar. */
|
|
35
|
+
formulaBar?: boolean;
|
|
36
|
+
negative?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A FORM column holding a literal (text / number / select / boolean). */
|
|
39
|
+
export interface LiteralTileContent {
|
|
40
|
+
kind: 'literal';
|
|
41
|
+
value: string;
|
|
42
|
+
topLeftLabel: string;
|
|
43
|
+
topRightLabel: string;
|
|
44
|
+
bottomLabel: string;
|
|
45
|
+
}
|
|
46
|
+
/** The blank "+" input tile. */
|
|
47
|
+
export interface EmptyTileContent {
|
|
48
|
+
kind: 'empty';
|
|
49
|
+
topLeftLabel?: string;
|
|
50
|
+
topRightLabel?: string;
|
|
51
|
+
bottomLabel?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface HeaderTileContent {
|
|
54
|
+
kind: 'header';
|
|
55
|
+
descriptor: GroupHeaderDescriptor;
|
|
56
|
+
}
|
|
57
|
+
export interface ObjectTileContent {
|
|
58
|
+
kind: 'object';
|
|
59
|
+
name: string;
|
|
60
|
+
/** FileUploadType value; decides the footer bar treatment. */
|
|
61
|
+
fileType?: string;
|
|
62
|
+
thumbnail?: EmbeddedImage;
|
|
63
|
+
}
|
|
64
|
+
export interface PlaneDimensionTileContent {
|
|
65
|
+
kind: 'planeDimension';
|
|
66
|
+
value: string;
|
|
67
|
+
unit: string;
|
|
68
|
+
}
|
|
69
|
+
export interface InstructionsTileContent {
|
|
70
|
+
kind: 'instructions';
|
|
71
|
+
text: string;
|
|
72
|
+
textColor?: string;
|
|
73
|
+
backgroundColor?: string;
|
|
74
|
+
}
|
|
75
|
+
export type ResolvedTileContent = MeasurementTileContent | LiteralTileContent | EmptyTileContent | HeaderTileContent | ObjectTileContent | PlaneDimensionTileContent | InstructionsTileContent;
|
|
76
|
+
export interface SkippedItem {
|
|
77
|
+
id: string;
|
|
78
|
+
kind: 'layer' | 'tile' | 'shape' | 'stroke' | 'text';
|
|
79
|
+
reason: string;
|
|
80
|
+
}
|
|
81
|
+
export interface RenderCanvasToPdfInput {
|
|
82
|
+
/** The original PDF the canvas is drawn over. */
|
|
83
|
+
sourcePdf: Uint8Array;
|
|
84
|
+
canvas: AnnotationCanvasState;
|
|
85
|
+
/** Content for each placed tile; `undefined` renders the blank tile. */
|
|
86
|
+
resolveTile: (ref: PlacedMeasurementRef) => ResolvedTileContent | undefined;
|
|
87
|
+
/** Decoded bytes per raster background layer id. Missing → skipped. */
|
|
88
|
+
layerImages: Record<string, EmbeddedImage>;
|
|
89
|
+
calibration?: PdfExportCalibration;
|
|
90
|
+
/** TTF/OTF bytes of the primary font (Metropolis Bold in the apps). */
|
|
91
|
+
primaryFont: Uint8Array;
|
|
92
|
+
/**
|
|
93
|
+
* 'annotations' (default): every layer, mark and tile becomes a PDF
|
|
94
|
+
* annotation object — selectable, movable and deletable in Preview /
|
|
95
|
+
* Acrobat, with the element's exact drawing as its appearance. 'flatten':
|
|
96
|
+
* everything is appended to the page content stream and cannot be edited.
|
|
97
|
+
*/
|
|
98
|
+
mode?: 'annotations' | 'flatten';
|
|
99
|
+
}
|
|
100
|
+
export interface RenderCanvasToPdfResult {
|
|
101
|
+
pdf: Uint8Array;
|
|
102
|
+
pageCount: number;
|
|
103
|
+
/** Annotations written across all pages (0 in flatten mode). */
|
|
104
|
+
annotationCount: number;
|
|
105
|
+
skipped: SkippedItem[];
|
|
106
|
+
}
|
|
107
|
+
export declare class PdfExportError extends Error {
|
|
108
|
+
readonly code: 'no-pdf-document' | 'page-count-mismatch' | 'page-geometry-mismatch'
|
|
109
|
+
/** The renderer was handed an encrypted PDF — run stripPdfEncryption first. */
|
|
110
|
+
| 'encrypted' | 'password-required' | 'password-incorrect' | 'unsupported' | 'corrupt';
|
|
111
|
+
constructor(code: PdfExportError['code'], message: string);
|
|
112
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public types of the canvas → PDF exporter. The renderer is PURE: it never
|
|
2
|
+
// touches Firestore or Storage. Whoever calls it (the Cloud Function) resolves
|
|
3
|
+
// every tile's content and every layer's bytes first and hands them in.
|
|
4
|
+
export class PdfExportError extends Error {
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'PdfExportError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/exports.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache,
|
|
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';
|
package/dist/exports.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// platform-specific `AnnotationCanvas` wrapper lives here. The two index
|
|
3
3
|
// files (index.ts, index.native.ts) re-export from this and add their own
|
|
4
4
|
// AnnotationCanvas import.
|
|
5
|
-
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache,
|
|
5
|
+
export { evaluateFormula, createFormulaScope, createEnhancedFormulaScope, clearFormulaCache, } from './formulas/evaluateFormula.js';
|
|
6
6
|
export { calculateFormula } from './formulas/calculateFormula.js';
|
|
7
7
|
export { convertMicrometers } from './utils/micrometersToUnit.js';
|
|
8
8
|
export { parseMeasurement } from './utils/parseMeasurement.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { ColumnConfig, DecimalTolerance, FractionalTolerance, Formula, Measurement, Units } from '../types/firestore.js';
|
|
2
|
-
export declare const calculateFormula: (formula: Formula, formulas: Formula[], columns: Record<string, string>, tableConfig: ColumnConfig[], measurements: Measurement[], unit: Units,
|
|
2
|
+
export declare const calculateFormula: (formula: Formula, formulas: Formula[], columns: Record<string, string>, tableConfig: ColumnConfig[], measurements: Measurement[], unit: Units, fractionalTolerance: FractionalTolerance, decimalTolerance: DecimalTolerance) => string | number | null;
|
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
import { ColumnType, } from '../types/firestore.js';
|
|
2
|
-
import { evaluateFormula
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { evaluateFormula } from './evaluateFormula.js';
|
|
3
|
+
import { convertMicrometers } from '../utils/micrometersToUnit.js';
|
|
4
|
+
function parseMixedNumber(str) {
|
|
5
|
+
const parts = str.trim().split(' ');
|
|
6
|
+
let whole = 0;
|
|
7
|
+
let fraction = 0;
|
|
8
|
+
if (parts.length === 2) {
|
|
9
|
+
whole = parseFloat(parts[0]);
|
|
10
|
+
const [num, den] = parts[1].split('/').map(Number);
|
|
11
|
+
fraction = num / den;
|
|
12
|
+
}
|
|
13
|
+
else if (parts[0].includes('/')) {
|
|
14
|
+
const [num, den] = parts[0].split('/').map(Number);
|
|
15
|
+
fraction = num / den;
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
whole = parseFloat(parts[0]);
|
|
19
|
+
}
|
|
20
|
+
return whole + fraction;
|
|
21
|
+
}
|
|
22
|
+
export const calculateFormula = (formula, formulas, columns, tableConfig, measurements, unit, fractionalTolerance, decimalTolerance) => {
|
|
7
23
|
// Convert Firestore Map to plain object if necessary
|
|
8
24
|
let variableToColumnMap = {};
|
|
9
25
|
if (formula.variableToColumnMap) {
|
|
@@ -29,11 +45,8 @@ _fractionalTolerance, _decimalTolerance) => {
|
|
|
29
45
|
const measurement = measurements.find((m) => m.id === measurementId);
|
|
30
46
|
if (!measurement)
|
|
31
47
|
continue;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// to the display tolerance and returned NaN / dropped the inches for the
|
|
35
|
-
// composite feet-inches formats (`5' 3 1/2"`).
|
|
36
|
-
valueMap[columnId] = micrometersToUnitNumber(measurement.value, unit);
|
|
48
|
+
const { value, unit: displayUnit } = convertMicrometers(measurement.value, unit, fractionalTolerance, decimalTolerance);
|
|
49
|
+
valueMap[columnId] = parseMixedNumber(value);
|
|
37
50
|
}
|
|
38
51
|
else if (column.type === ColumnType.Angle) {
|
|
39
52
|
const measurement = measurements.find((m) => m.id === measurementId);
|
|
@@ -17,16 +17,6 @@ export interface FormulaEvaluationOptions {
|
|
|
17
17
|
formulas?: FormulaDefinition[];
|
|
18
18
|
scope?: Record<string, number>;
|
|
19
19
|
}
|
|
20
|
-
export declare function normalizeUnitForMathJS(unit: Units | string): string;
|
|
21
|
-
/**
|
|
22
|
-
* Convert a stored micrometer value to the number the formula scope expects
|
|
23
|
-
* for `unit` — the same conversion nested-formula results go through. This is
|
|
24
|
-
* the ONLY sanctioned way to put a measurement into a valueMap: converting via
|
|
25
|
-
* the display STRING (convertMicrometers + a string parser) both rounds the
|
|
26
|
-
* value to the display tolerance and falls apart on composite formats like
|
|
27
|
-
* `5' 3 1/2"`.
|
|
28
|
-
*/
|
|
29
|
-
export declare function micrometersToUnitNumber(micrometers: number, unit: Units): number;
|
|
30
20
|
export declare function evaluateFormula({ expression, mappings, valueMap, unit, formulas, scope, }: FormulaEvaluationOptions): number | string | null;
|
|
31
21
|
export declare function createFormulaScope({ mappings, valueMap, unit, }: {
|
|
32
22
|
mappings: Record<string, string>;
|
|
@@ -7,7 +7,7 @@ const compile = math.compile.bind(math);
|
|
|
7
7
|
const mathUnit = math.unit.bind(math);
|
|
8
8
|
// Cache for evaluated formulas to improve performance
|
|
9
9
|
const formulaCache = new Map();
|
|
10
|
-
|
|
10
|
+
function normalizeUnitForMathJS(unit) {
|
|
11
11
|
switch (unit) {
|
|
12
12
|
case Units.Centimeters:
|
|
13
13
|
case 'cm':
|
|
@@ -30,17 +30,6 @@ export function normalizeUnitForMathJS(unit) {
|
|
|
30
30
|
return 'mm'; // fallback
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Convert a stored micrometer value to the number the formula scope expects
|
|
35
|
-
* for `unit` — the same conversion nested-formula results go through. This is
|
|
36
|
-
* the ONLY sanctioned way to put a measurement into a valueMap: converting via
|
|
37
|
-
* the display STRING (convertMicrometers + a string parser) both rounds the
|
|
38
|
-
* value to the display tolerance and falls apart on composite formats like
|
|
39
|
-
* `5' 3 1/2"`.
|
|
40
|
-
*/
|
|
41
|
-
export function micrometersToUnitNumber(micrometers, unit) {
|
|
42
|
-
return mathUnit(micrometers, 'um').toNumber(normalizeUnitForMathJS(unit));
|
|
43
|
-
}
|
|
44
33
|
// Normalize mapping to EnhancedMapping format
|
|
45
34
|
function normalizeMapping(mapping) {
|
|
46
35
|
if (typeof mapping === 'string') {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Units } from '../types/firestore.js';
|
|
2
|
+
/** Frame types of the stack-expression formula the apps persist. */
|
|
3
|
+
export declare enum GroupFormulaFrameType {
|
|
4
|
+
Operator = "operator",
|
|
5
|
+
Number = "number",
|
|
6
|
+
Paren = "paren",
|
|
7
|
+
Fraction = "fraction",
|
|
8
|
+
FeetInches = "feetInches",
|
|
9
|
+
MeasurementObj = "measurement",
|
|
10
|
+
InspectionInput = "InspectionInput",
|
|
11
|
+
Func = "func",
|
|
12
|
+
Output = "output"
|
|
13
|
+
}
|
|
14
|
+
export interface GroupFormulaFraction {
|
|
15
|
+
int: string;
|
|
16
|
+
numerator: string;
|
|
17
|
+
denominator: string;
|
|
18
|
+
}
|
|
19
|
+
export interface GroupFormulaFeetInches {
|
|
20
|
+
feet: string;
|
|
21
|
+
int: string;
|
|
22
|
+
numerator: string;
|
|
23
|
+
denominator: string;
|
|
24
|
+
}
|
|
25
|
+
export interface GroupFormulaFrame {
|
|
26
|
+
type: GroupFormulaFrameType | string;
|
|
27
|
+
value: unknown;
|
|
28
|
+
measurement?: {
|
|
29
|
+
value: number;
|
|
30
|
+
};
|
|
31
|
+
func?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare const micrometersToUnitNumber: (um: number, unit: Units) => number;
|
|
34
|
+
export declare const unitNumberToMicrometers: (value: number, unit: Units) => number;
|
|
35
|
+
/** The mathjs expression string a stack formula evaluates to, for `valueUm`. */
|
|
36
|
+
export declare const groupFormulaExpression: (frames: GroupFormulaFrame[], valueUm: number, unit: Units | undefined) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Apply a group's stored `formula` to a measurement value (micrometers) and
|
|
39
|
+
* return the result in micrometers, or null when the group has no formula or
|
|
40
|
+
* the expression does not evaluate to a finite number (the caller falls back
|
|
41
|
+
* to the raw value, exactly like the tiles do).
|
|
42
|
+
*/
|
|
43
|
+
export declare const applyGroupFormulaUm: (formula: unknown, valueUm: number, unit: Units | undefined) => number | null;
|
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
3
|
+
"version": "1.23.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,10 +47,14 @@
|
|
|
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
|
},
|
|
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.",
|
|
53
56
|
"peerDependencies": {
|
|
57
|
+
"@embedpdf/pdfium": ">=2.15.0",
|
|
54
58
|
"@shopify/react-native-skia": ">=2.5.0",
|
|
55
59
|
"react": "^18.0.0 || ^19.0.0",
|
|
56
60
|
"react-native": ">=0.74.0",
|
|
@@ -58,6 +62,9 @@
|
|
|
58
62
|
"react-native-reanimated": ">=3.0.0"
|
|
59
63
|
},
|
|
60
64
|
"peerDependenciesMeta": {
|
|
65
|
+
"@embedpdf/pdfium": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
61
68
|
"react-native": {
|
|
62
69
|
"optional": true
|
|
63
70
|
},
|
|
@@ -70,6 +77,7 @@
|
|
|
70
77
|
},
|
|
71
78
|
"devDependencies": {
|
|
72
79
|
"@arethetypeswrong/cli": "^0.18.1",
|
|
80
|
+
"@embedpdf/pdfium": "2.15.0",
|
|
73
81
|
"@shopify/react-native-skia": "^2.6.2",
|
|
74
82
|
"@types/node": "^22.15.18",
|
|
75
83
|
"@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;
|