@twinfinity/printing 6.0.1-ci.28952-beta → 6.0.2-beta
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/README.md +3 -3
- package/dist/BuildInfo.js +3 -3
- package/dist/BuildInfo.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/analyzeTemplate.js.map +1 -1
- package/dist/layout/clipUtils.js.map +1 -1
- package/dist/layout/colorConstants.js.map +1 -1
- package/dist/layout/elementKinds.js.map +1 -1
- package/dist/layout/exportDrawing.js.map +1 -1
- package/dist/layout/filters.js.map +1 -1
- package/dist/layout/geometryConstants.js.map +1 -1
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/itemBuilders.js.map +1 -1
- package/dist/layout/labelsRender.js.map +1 -1
- package/dist/layout/legendConstants.js.map +1 -1
- package/dist/layout/legendRender.js.map +1 -1
- package/dist/layout/legendRows.js.map +1 -1
- package/dist/layout/legendUtils.js.map +1 -1
- package/dist/layout/metadata.js.map +1 -1
- package/dist/layout/model.js.map +1 -1
- package/dist/layout/options.js.map +1 -1
- package/dist/layout/presets.js.map +1 -1
- package/dist/layout/qrRender.js.map +1 -1
- package/dist/layout/renderDrawing.js.map +1 -1
- package/dist/layout/renderUtils.js.map +1 -1
- package/dist/layout/sectionValidation.js.map +1 -1
- package/dist/layout/templateVariables.js.map +1 -1
- package/dist/layout/typeGuards.js.map +1 -1
- package/dist/layout/types.js.map +1 -1
- package/dist/layout/utils.js.map +1 -1
- package/dist/layout/validation.js.map +1 -1
- package/dist/layout/validationHelpers.js.map +1 -1
- package/dist/layout/validationUtils.js.map +1 -1
- package/dist/layout/viewportPrep.js.map +1 -1
- package/dist/layout/viewportRender.js.map +1 -1
- package/dist/layout/viewportUtils.js.map +1 -1
- package/dist/layout/warnings.js.map +1 -1
- package/dist/printToPdf.js.map +1 -1
- package/dist/printToSvg.js.map +1 -1
- package/dist/sections.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/BuildInfo.ts +41 -41
- package/src/index.ts +10 -10
- package/src/layout/analyzeTemplate.ts +218 -218
- package/src/layout/clipUtils.ts +193 -193
- package/src/layout/colorConstants.ts +16 -16
- package/src/layout/elementKinds.ts +35 -35
- package/src/layout/exportDrawing.ts +206 -206
- package/src/layout/filters.ts +80 -80
- package/src/layout/geometryConstants.ts +11 -11
- package/src/layout/index.ts +671 -671
- package/src/layout/itemBuilders.ts +159 -159
- package/src/layout/labelsRender.ts +170 -170
- package/src/layout/legendConstants.ts +11 -11
- package/src/layout/legendRender.ts +543 -543
- package/src/layout/legendRows.ts +62 -62
- package/src/layout/legendUtils.ts +144 -144
- package/src/layout/metadata.ts +210 -210
- package/src/layout/model.ts +372 -372
- package/src/layout/options.ts +17 -17
- package/src/layout/presets.ts +51 -51
- package/src/layout/qrRender.ts +126 -126
- package/src/layout/qrcode.d.ts +2 -2
- package/src/layout/renderDrawing.ts +710 -710
- package/src/layout/renderUtils.ts +138 -138
- package/src/layout/sectionValidation.ts +2 -2
- package/src/layout/templateVariables.ts +317 -317
- package/src/layout/typeGuards.ts +9 -9
- package/src/layout/types.ts +835 -835
- package/src/layout/utils.ts +85 -85
- package/src/layout/validation.ts +392 -392
- package/src/layout/validationHelpers.ts +59 -59
- package/src/layout/validationUtils.ts +6 -6
- package/src/layout/viewportPrep.ts +351 -351
- package/src/layout/viewportRender.ts +442 -442
- package/src/layout/viewportUtils.ts +50 -50
- package/src/layout/warnings.ts +3 -3
- package/src/printToPdf.ts +108 -108
- package/src/printToSvg.ts +188 -188
- package/src/sections.ts +1019 -1019
- package/src/types.ts +49 -49
package/src/layout/options.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { ModelMetadata } from './metadata';
|
|
2
|
-
|
|
3
|
-
export interface RenderLayoutOptions {
|
|
4
|
-
scaleDenominators?: number[];
|
|
5
|
-
maxViewports?: number;
|
|
6
|
-
allowRemoteAssets?: boolean;
|
|
7
|
-
allowDataUrls?: boolean;
|
|
8
|
-
qrContext?: {
|
|
9
|
-
host?: string | null;
|
|
10
|
-
containerName?: string | null;
|
|
11
|
-
floorName?: string | null;
|
|
12
|
-
planeHeight?: number | null;
|
|
13
|
-
};
|
|
14
|
-
legendSources?: Record<string, any[]>;
|
|
15
|
-
/** Optional metadata map keyed by productId referenced from path/label meta */
|
|
16
|
-
metadataById?: ModelMetadata;
|
|
17
|
-
}
|
|
1
|
+
import { ModelMetadata } from './metadata';
|
|
2
|
+
|
|
3
|
+
export interface RenderLayoutOptions {
|
|
4
|
+
scaleDenominators?: number[];
|
|
5
|
+
maxViewports?: number;
|
|
6
|
+
allowRemoteAssets?: boolean;
|
|
7
|
+
allowDataUrls?: boolean;
|
|
8
|
+
qrContext?: {
|
|
9
|
+
host?: string | null;
|
|
10
|
+
containerName?: string | null;
|
|
11
|
+
floorName?: string | null;
|
|
12
|
+
planeHeight?: number | null;
|
|
13
|
+
};
|
|
14
|
+
legendSources?: Record<string, any[]>;
|
|
15
|
+
/** Optional metadata map keyed by productId referenced from path/label meta */
|
|
16
|
+
metadataById?: ModelMetadata;
|
|
17
|
+
}
|
package/src/layout/presets.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
export const DEFAULT_SCALE_DENOMS = [2, 10, 25, 50, 100, 250, 500, 1000, 2500] as const;
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Example building class preset. Not auto-applied — consumers must include it
|
|
5
|
-
* in their template's `presets.includeClasses` if they want to use it.
|
|
6
|
-
*
|
|
7
|
-
* ```json
|
|
8
|
-
* { "presets": { "includeClasses": { "defaultBuilding": [...] } } }
|
|
9
|
-
* ```
|
|
10
|
-
*/
|
|
11
|
-
export const EXAMPLE_BUILDING_CLASSES: string[] = [
|
|
12
|
-
'space',
|
|
13
|
-
'ifcspace',
|
|
14
|
-
'wall',
|
|
15
|
-
'walls',
|
|
16
|
-
'ifcwall',
|
|
17
|
-
'ifcwallstandardcase',
|
|
18
|
-
'window',
|
|
19
|
-
'windows',
|
|
20
|
-
'ifcwindow',
|
|
21
|
-
'door',
|
|
22
|
-
'doors',
|
|
23
|
-
'ifcdoor',
|
|
24
|
-
'beam',
|
|
25
|
-
'ifcbeam',
|
|
26
|
-
'column',
|
|
27
|
-
'columns',
|
|
28
|
-
'ifccolumn',
|
|
29
|
-
'stair',
|
|
30
|
-
'stairs',
|
|
31
|
-
'ifcstair',
|
|
32
|
-
'slab',
|
|
33
|
-
'ifcslab'
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const defaultScalePresets: Record<string, { preferredScales?: number[] }> = {
|
|
37
|
-
default: { preferredScales: Array.from(DEFAULT_SCALE_DENOMS) }
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export interface ResolvedPresets {
|
|
41
|
-
includeClasses: Record<string, string[]>;
|
|
42
|
-
scales: Record<string, { preferredScales?: number[] }>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const resolvePresets = (presets?: {
|
|
46
|
-
includeClasses?: Record<string, string[]>;
|
|
47
|
-
scales?: Record<string, { preferredScales?: number[] }>;
|
|
48
|
-
}): ResolvedPresets => ({
|
|
49
|
-
includeClasses: presets?.includeClasses ?? {},
|
|
50
|
-
scales: { ...defaultScalePresets, ...(presets?.scales ?? {}) }
|
|
51
|
-
});
|
|
1
|
+
export const DEFAULT_SCALE_DENOMS = [2, 10, 25, 50, 100, 250, 500, 1000, 2500] as const;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example building class preset. Not auto-applied — consumers must include it
|
|
5
|
+
* in their template's `presets.includeClasses` if they want to use it.
|
|
6
|
+
*
|
|
7
|
+
* ```json
|
|
8
|
+
* { "presets": { "includeClasses": { "defaultBuilding": [...] } } }
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export const EXAMPLE_BUILDING_CLASSES: string[] = [
|
|
12
|
+
'space',
|
|
13
|
+
'ifcspace',
|
|
14
|
+
'wall',
|
|
15
|
+
'walls',
|
|
16
|
+
'ifcwall',
|
|
17
|
+
'ifcwallstandardcase',
|
|
18
|
+
'window',
|
|
19
|
+
'windows',
|
|
20
|
+
'ifcwindow',
|
|
21
|
+
'door',
|
|
22
|
+
'doors',
|
|
23
|
+
'ifcdoor',
|
|
24
|
+
'beam',
|
|
25
|
+
'ifcbeam',
|
|
26
|
+
'column',
|
|
27
|
+
'columns',
|
|
28
|
+
'ifccolumn',
|
|
29
|
+
'stair',
|
|
30
|
+
'stairs',
|
|
31
|
+
'ifcstair',
|
|
32
|
+
'slab',
|
|
33
|
+
'ifcslab'
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const defaultScalePresets: Record<string, { preferredScales?: number[] }> = {
|
|
37
|
+
default: { preferredScales: Array.from(DEFAULT_SCALE_DENOMS) }
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export interface ResolvedPresets {
|
|
41
|
+
includeClasses: Record<string, string[]>;
|
|
42
|
+
scales: Record<string, { preferredScales?: number[] }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const resolvePresets = (presets?: {
|
|
46
|
+
includeClasses?: Record<string, string[]>;
|
|
47
|
+
scales?: Record<string, { preferredScales?: number[] }>;
|
|
48
|
+
}): ResolvedPresets => ({
|
|
49
|
+
includeClasses: presets?.includeClasses ?? {},
|
|
50
|
+
scales: { ...defaultScalePresets, ...(presets?.scales ?? {}) }
|
|
51
|
+
});
|
package/src/layout/qrRender.ts
CHANGED
|
@@ -1,126 +1,126 @@
|
|
|
1
|
-
import { escapeAttr } from './renderUtils';
|
|
2
|
-
import { interpolate } from './renderUtils';
|
|
3
|
-
import { QrElement, SvgModelView } from './types';
|
|
4
|
-
|
|
5
|
-
async function getQRCode(): Promise<typeof import('qrcode')> {
|
|
6
|
-
try {
|
|
7
|
-
return await import('qrcode');
|
|
8
|
-
} catch {
|
|
9
|
-
throw new Error('qrcode is required for QR code rendering. Install it: bun add qrcode');
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface QrContext {
|
|
14
|
-
host?: string | null;
|
|
15
|
-
containerName?: string | null;
|
|
16
|
-
floorName?: string | null;
|
|
17
|
-
planeHeight?: number | null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const renderQrElements = async (
|
|
21
|
-
qrElements: QrElement[],
|
|
22
|
-
viewports: any[],
|
|
23
|
-
viewportScales: Record<string, { mmPerModelUnit: number }>,
|
|
24
|
-
models: Record<string, SvgModelView | undefined>,
|
|
25
|
-
options: { qrContext?: QrContext },
|
|
26
|
-
dataWithViewport: any,
|
|
27
|
-
warnings: string[]
|
|
28
|
-
): Promise<string[]> => {
|
|
29
|
-
const parts: string[] = [];
|
|
30
|
-
|
|
31
|
-
for (const qr of qrElements) {
|
|
32
|
-
const boundViewportId = qr.viewport ?? viewports[0]?.id ?? null;
|
|
33
|
-
const viewportScale = boundViewportId ? viewportScales[boundViewportId] : null;
|
|
34
|
-
const boundViewportDef = boundViewportId ? viewports.find((v) => v.id === boundViewportId) : null;
|
|
35
|
-
// In v2, viewport id IS the model key
|
|
36
|
-
const boundModel = boundViewportDef ? (models[boundViewportDef.id] ?? null) : null;
|
|
37
|
-
|
|
38
|
-
let coordinate: { x: number; y: number; z: number } | null = null;
|
|
39
|
-
if (boundViewportDef && viewportScale && boundModel?.transform && (boundViewportDef.rotationDeg ?? 0) === 0) {
|
|
40
|
-
const mmPerModel = viewportScale.mmPerModelUnit;
|
|
41
|
-
const contentWidthMm = boundModel.widthModel * mmPerModel;
|
|
42
|
-
const contentHeightMm = boundModel.heightModel * mmPerModel;
|
|
43
|
-
const centerX =
|
|
44
|
-
boundViewportDef.align === 'topLeft'
|
|
45
|
-
? boundViewportDef.xMm + contentWidthMm * 0.5
|
|
46
|
-
: boundViewportDef.xMm + boundViewportDef.widthMm * 0.5;
|
|
47
|
-
const centerY =
|
|
48
|
-
boundViewportDef.align === 'topLeft'
|
|
49
|
-
? boundViewportDef.yMm + contentHeightMm * 0.5
|
|
50
|
-
: boundViewportDef.yMm + boundViewportDef.heightMm * 0.5;
|
|
51
|
-
|
|
52
|
-
const pointMmX = qr.xMm + qr.sizeMm * 0.5;
|
|
53
|
-
const pointMmY = qr.yMm + qr.sizeMm * 0.5;
|
|
54
|
-
|
|
55
|
-
const modelX = (pointMmX - centerX) / mmPerModel + boundModel.widthModel * 0.5;
|
|
56
|
-
const modelY = (pointMmY - centerY) / mmPerModel + boundModel.heightModel * 0.5;
|
|
57
|
-
|
|
58
|
-
const t = boundModel.transform;
|
|
59
|
-
const worldX = t.viewMinX + modelX;
|
|
60
|
-
const worldZ = t.viewMaxZ - modelY;
|
|
61
|
-
const planeHeight = options?.qrContext?.planeHeight ?? 0;
|
|
62
|
-
coordinate = { x: formatNumber(worldX), y: formatNumber(planeHeight ?? 0), z: formatNumber(worldZ) };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let payload: unknown = qr.dataTemplate ? interpolate(qr.dataTemplate, dataWithViewport) : null;
|
|
66
|
-
if (!payload || typeof payload === 'string') {
|
|
67
|
-
if (qr.role === 'anchor' && coordinate) {
|
|
68
|
-
const corner =
|
|
69
|
-
qr.anchorCorner === 'bottom-left'
|
|
70
|
-
? 'bl'
|
|
71
|
-
: qr.anchorCorner === 'bottom-right'
|
|
72
|
-
? 'br'
|
|
73
|
-
: qr.anchorCorner === 'top-left'
|
|
74
|
-
? 'tl'
|
|
75
|
-
: qr.anchorCorner === 'top-right'
|
|
76
|
-
? 'tr'
|
|
77
|
-
: 'na';
|
|
78
|
-
payload = { v: 1, c: corner, p: coordinate };
|
|
79
|
-
} else if (qr.role === 'main') {
|
|
80
|
-
const cornerShort =
|
|
81
|
-
qr.anchorCorner === 'bottom-left'
|
|
82
|
-
? 'bl'
|
|
83
|
-
: qr.anchorCorner === 'bottom-right'
|
|
84
|
-
? 'br'
|
|
85
|
-
: qr.anchorCorner === 'top-left'
|
|
86
|
-
? 'tl'
|
|
87
|
-
: qr.anchorCorner === 'top-right'
|
|
88
|
-
? 'tr'
|
|
89
|
-
: 'bl';
|
|
90
|
-
payload = {
|
|
91
|
-
v: 1,
|
|
92
|
-
c: cornerShort,
|
|
93
|
-
h: options?.qrContext?.host ?? null,
|
|
94
|
-
a: options?.qrContext?.containerName ?? null,
|
|
95
|
-
fn: options?.qrContext?.floorName ?? null,
|
|
96
|
-
p: coordinate
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!payload) {
|
|
102
|
-
warnings.push(`QR '${qr.id}' has no payload.`);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const QRCode = await getQRCode();
|
|
108
|
-
const dataUrl = await QRCode.toDataURL(typeof payload === 'string' ? payload : JSON.stringify(payload), {
|
|
109
|
-
margin: 0,
|
|
110
|
-
errorCorrectionLevel: 'M'
|
|
111
|
-
});
|
|
112
|
-
parts.push(
|
|
113
|
-
`<image href="${escapeAttr(dataUrl)}" x="${qr.xMm}" y="${qr.yMm}" width="${qr.sizeMm}" height="${qr.sizeMm}" />`
|
|
114
|
-
);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
warnings.push(`Failed to generate QR '${qr.id}': ${(error as Error).message}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return parts;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const formatNumber = (value: number): number => {
|
|
124
|
-
if (!Number.isFinite(value)) return 0;
|
|
125
|
-
return Number.parseFloat(value.toFixed(3));
|
|
126
|
-
};
|
|
1
|
+
import { escapeAttr } from './renderUtils';
|
|
2
|
+
import { interpolate } from './renderUtils';
|
|
3
|
+
import { QrElement, SvgModelView } from './types';
|
|
4
|
+
|
|
5
|
+
async function getQRCode(): Promise<typeof import('qrcode')> {
|
|
6
|
+
try {
|
|
7
|
+
return await import('qrcode');
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error('qrcode is required for QR code rendering. Install it: bun add qrcode');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QrContext {
|
|
14
|
+
host?: string | null;
|
|
15
|
+
containerName?: string | null;
|
|
16
|
+
floorName?: string | null;
|
|
17
|
+
planeHeight?: number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const renderQrElements = async (
|
|
21
|
+
qrElements: QrElement[],
|
|
22
|
+
viewports: any[],
|
|
23
|
+
viewportScales: Record<string, { mmPerModelUnit: number }>,
|
|
24
|
+
models: Record<string, SvgModelView | undefined>,
|
|
25
|
+
options: { qrContext?: QrContext },
|
|
26
|
+
dataWithViewport: any,
|
|
27
|
+
warnings: string[]
|
|
28
|
+
): Promise<string[]> => {
|
|
29
|
+
const parts: string[] = [];
|
|
30
|
+
|
|
31
|
+
for (const qr of qrElements) {
|
|
32
|
+
const boundViewportId = qr.viewport ?? viewports[0]?.id ?? null;
|
|
33
|
+
const viewportScale = boundViewportId ? viewportScales[boundViewportId] : null;
|
|
34
|
+
const boundViewportDef = boundViewportId ? viewports.find((v) => v.id === boundViewportId) : null;
|
|
35
|
+
// In v2, viewport id IS the model key
|
|
36
|
+
const boundModel = boundViewportDef ? (models[boundViewportDef.id] ?? null) : null;
|
|
37
|
+
|
|
38
|
+
let coordinate: { x: number; y: number; z: number } | null = null;
|
|
39
|
+
if (boundViewportDef && viewportScale && boundModel?.transform && (boundViewportDef.rotationDeg ?? 0) === 0) {
|
|
40
|
+
const mmPerModel = viewportScale.mmPerModelUnit;
|
|
41
|
+
const contentWidthMm = boundModel.widthModel * mmPerModel;
|
|
42
|
+
const contentHeightMm = boundModel.heightModel * mmPerModel;
|
|
43
|
+
const centerX =
|
|
44
|
+
boundViewportDef.align === 'topLeft'
|
|
45
|
+
? boundViewportDef.xMm + contentWidthMm * 0.5
|
|
46
|
+
: boundViewportDef.xMm + boundViewportDef.widthMm * 0.5;
|
|
47
|
+
const centerY =
|
|
48
|
+
boundViewportDef.align === 'topLeft'
|
|
49
|
+
? boundViewportDef.yMm + contentHeightMm * 0.5
|
|
50
|
+
: boundViewportDef.yMm + boundViewportDef.heightMm * 0.5;
|
|
51
|
+
|
|
52
|
+
const pointMmX = qr.xMm + qr.sizeMm * 0.5;
|
|
53
|
+
const pointMmY = qr.yMm + qr.sizeMm * 0.5;
|
|
54
|
+
|
|
55
|
+
const modelX = (pointMmX - centerX) / mmPerModel + boundModel.widthModel * 0.5;
|
|
56
|
+
const modelY = (pointMmY - centerY) / mmPerModel + boundModel.heightModel * 0.5;
|
|
57
|
+
|
|
58
|
+
const t = boundModel.transform;
|
|
59
|
+
const worldX = t.viewMinX + modelX;
|
|
60
|
+
const worldZ = t.viewMaxZ - modelY;
|
|
61
|
+
const planeHeight = options?.qrContext?.planeHeight ?? 0;
|
|
62
|
+
coordinate = { x: formatNumber(worldX), y: formatNumber(planeHeight ?? 0), z: formatNumber(worldZ) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let payload: unknown = qr.dataTemplate ? interpolate(qr.dataTemplate, dataWithViewport) : null;
|
|
66
|
+
if (!payload || typeof payload === 'string') {
|
|
67
|
+
if (qr.role === 'anchor' && coordinate) {
|
|
68
|
+
const corner =
|
|
69
|
+
qr.anchorCorner === 'bottom-left'
|
|
70
|
+
? 'bl'
|
|
71
|
+
: qr.anchorCorner === 'bottom-right'
|
|
72
|
+
? 'br'
|
|
73
|
+
: qr.anchorCorner === 'top-left'
|
|
74
|
+
? 'tl'
|
|
75
|
+
: qr.anchorCorner === 'top-right'
|
|
76
|
+
? 'tr'
|
|
77
|
+
: 'na';
|
|
78
|
+
payload = { v: 1, c: corner, p: coordinate };
|
|
79
|
+
} else if (qr.role === 'main') {
|
|
80
|
+
const cornerShort =
|
|
81
|
+
qr.anchorCorner === 'bottom-left'
|
|
82
|
+
? 'bl'
|
|
83
|
+
: qr.anchorCorner === 'bottom-right'
|
|
84
|
+
? 'br'
|
|
85
|
+
: qr.anchorCorner === 'top-left'
|
|
86
|
+
? 'tl'
|
|
87
|
+
: qr.anchorCorner === 'top-right'
|
|
88
|
+
? 'tr'
|
|
89
|
+
: 'bl';
|
|
90
|
+
payload = {
|
|
91
|
+
v: 1,
|
|
92
|
+
c: cornerShort,
|
|
93
|
+
h: options?.qrContext?.host ?? null,
|
|
94
|
+
a: options?.qrContext?.containerName ?? null,
|
|
95
|
+
fn: options?.qrContext?.floorName ?? null,
|
|
96
|
+
p: coordinate
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!payload) {
|
|
102
|
+
warnings.push(`QR '${qr.id}' has no payload.`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const QRCode = await getQRCode();
|
|
108
|
+
const dataUrl = await QRCode.toDataURL(typeof payload === 'string' ? payload : JSON.stringify(payload), {
|
|
109
|
+
margin: 0,
|
|
110
|
+
errorCorrectionLevel: 'M'
|
|
111
|
+
});
|
|
112
|
+
parts.push(
|
|
113
|
+
`<image href="${escapeAttr(dataUrl)}" x="${qr.xMm}" y="${qr.yMm}" width="${qr.sizeMm}" height="${qr.sizeMm}" />`
|
|
114
|
+
);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
warnings.push(`Failed to generate QR '${qr.id}': ${(error as Error).message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return parts;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const formatNumber = (value: number): number => {
|
|
124
|
+
if (!Number.isFinite(value)) return 0;
|
|
125
|
+
return Number.parseFloat(value.toFixed(3));
|
|
126
|
+
};
|
package/src/layout/qrcode.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// @ts-ignore
|
|
2
|
-
declare module 'qrcode' { function toDataURL(text: string, options?: any): Promise<string>; }
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
declare module 'qrcode' { function toDataURL(text: string, options?: any): Promise<string>; }
|