@zseven-w/pen-core 0.5.2 → 0.7.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/README.md +18 -9
- package/package.json +5 -5
- package/src/__tests__/arc-path.test.ts +23 -23
- package/src/__tests__/codegen-utils.test.ts +50 -0
- package/src/__tests__/design-md-parser.test.ts +49 -0
- package/src/__tests__/font-utils.test.ts +15 -15
- package/src/__tests__/layout-engine.test.ts +169 -83
- package/src/__tests__/merge-helpers.test.ts +143 -0
- package/src/__tests__/node-diff.test.ts +139 -0
- package/src/__tests__/node-helpers.test.ts +19 -19
- package/src/__tests__/node-merge.test.ts +425 -0
- package/src/__tests__/normalize-stroke-fill-schema.test.ts +416 -0
- package/src/__tests__/normalize-tree-layout.test.ts +294 -0
- package/src/__tests__/normalize.test.ts +119 -80
- package/src/__tests__/path-anchors.test.ts +98 -0
- package/src/__tests__/resolve-variables-recursive.test.ts +173 -0
- package/src/__tests__/strip-redundant-section-fills.test.ts +278 -0
- package/src/__tests__/text-measure.test.ts +84 -79
- package/src/__tests__/tree-utils.test.ts +133 -102
- package/src/__tests__/unwrap-fake-phone-mockup.test.ts +322 -0
- package/src/__tests__/variables.test.ts +68 -65
- package/src/arc-path.ts +35 -35
- package/src/boolean-ops.ts +158 -111
- package/src/constants.ts +36 -36
- package/src/design-md-parser.ts +363 -0
- package/src/font-utils.ts +30 -15
- package/src/id.ts +1 -1
- package/src/index.ts +47 -13
- package/src/layout/engine.ts +255 -230
- package/src/layout/normalize-tree.ts +140 -0
- package/src/layout/strip-redundant-section-fills.ts +155 -0
- package/src/layout/text-measure.ts +133 -105
- package/src/layout/unwrap-fake-phone-mockup.ts +147 -0
- package/src/merge/index.ts +16 -0
- package/src/merge/merge-helpers.ts +113 -0
- package/src/merge/node-diff.ts +123 -0
- package/src/merge/node-merge.ts +651 -0
- package/src/node-helpers.ts +18 -5
- package/src/normalize/normalize-stroke-fill-schema.ts +297 -0
- package/src/normalize.ts +79 -79
- package/src/path-anchors.ts +331 -0
- package/src/sync-lock.ts +3 -3
- package/src/tree-utils.ts +180 -158
- package/src/variables/replace-refs.ts +63 -60
- package/src/variables/resolve.ts +107 -102
package/src/boolean-ops.ts
CHANGED
|
@@ -1,22 +1,77 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { PenNode, PathNode } from '@zseven-w/pen-types'
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import type { PenNode, PathNode } from '@zseven-w/pen-types';
|
|
4
3
|
|
|
5
|
-
export type BooleanOpType = 'union' | 'subtract' | 'intersect'
|
|
4
|
+
export type BooleanOpType = 'union' | 'subtract' | 'intersect';
|
|
6
5
|
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
8
7
|
// Paper.js scope — headless (no canvas needed)
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
interface PaperBoundsLike {
|
|
11
|
+
center: unknown;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PaperPathItem {
|
|
19
|
+
bounds: PaperBoundsLike;
|
|
20
|
+
pathData: string;
|
|
21
|
+
translate: (point: unknown) => void;
|
|
22
|
+
rotate: (angle: number, center: unknown) => void;
|
|
23
|
+
unite: (path: PaperPathItem) => PaperPathItem;
|
|
24
|
+
subtract: (path: PaperPathItem) => PaperPathItem;
|
|
25
|
+
intersect: (path: PaperPathItem) => PaperPathItem;
|
|
26
|
+
remove: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface PaperScope {
|
|
30
|
+
setup: (size: unknown) => void;
|
|
31
|
+
activate: () => void;
|
|
32
|
+
Size: new (width: number, height: number) => unknown;
|
|
33
|
+
Point: new (x: number, y: number) => unknown;
|
|
34
|
+
CompoundPath: {
|
|
35
|
+
create: (pathData: string) => PaperPathItem;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
12
38
|
|
|
13
|
-
|
|
39
|
+
interface PaperModule {
|
|
40
|
+
PaperScope: new () => PaperScope;
|
|
41
|
+
Point: new (x: number, y: number) => unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let paperModule: PaperModule | null | undefined;
|
|
45
|
+
let scope: PaperScope | null = null;
|
|
46
|
+
|
|
47
|
+
function getPaperModule(): PaperModule | null {
|
|
48
|
+
if (paperModule !== undefined) return paperModule;
|
|
49
|
+
try {
|
|
50
|
+
// Indirect require via globalThis to load paper.js at runtime without
|
|
51
|
+
// triggering esbuild's direct-eval warning. The assignment to globalThis
|
|
52
|
+
// happens once; subsequent calls read from the cached paperModule.
|
|
53
|
+
const _r =
|
|
54
|
+
((globalThis as any)['require'] as NodeRequire | undefined) ??
|
|
55
|
+
(typeof require !== 'undefined' ? require : undefined);
|
|
56
|
+
if (!_r) throw new Error('require not available');
|
|
57
|
+
paperModule = _r('paper') as PaperModule;
|
|
58
|
+
} catch {
|
|
59
|
+
paperModule = null;
|
|
60
|
+
}
|
|
61
|
+
return paperModule;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getScope(): PaperScope {
|
|
65
|
+
const paper = getPaperModule();
|
|
66
|
+
if (!paper) {
|
|
67
|
+
throw new Error('paper runtime is unavailable');
|
|
68
|
+
}
|
|
14
69
|
if (!scope) {
|
|
15
|
-
scope = new paper.PaperScope()
|
|
16
|
-
scope.setup(new scope.Size(1, 1))
|
|
70
|
+
scope = new paper.PaperScope();
|
|
71
|
+
scope.setup(new scope.Size(1, 1));
|
|
17
72
|
}
|
|
18
|
-
scope.activate()
|
|
19
|
-
return scope
|
|
73
|
+
scope.activate();
|
|
74
|
+
return scope;
|
|
20
75
|
}
|
|
21
76
|
|
|
22
77
|
// ---------------------------------------------------------------------------
|
|
@@ -24,31 +79,26 @@ function getScope(): paper.PaperScope {
|
|
|
24
79
|
// ---------------------------------------------------------------------------
|
|
25
80
|
|
|
26
81
|
function sizeVal(v: number | string | undefined, fallback: number): number {
|
|
27
|
-
if (typeof v === 'number') return v
|
|
82
|
+
if (typeof v === 'number') return v;
|
|
28
83
|
if (typeof v === 'string') {
|
|
29
|
-
const m = v.match(/\((\d+(?:\.\d+)?)\)/)
|
|
30
|
-
if (m) return parseFloat(m[1])
|
|
31
|
-
const n = parseFloat(v)
|
|
32
|
-
if (!isNaN(n)) return n
|
|
84
|
+
const m = v.match(/\((\d+(?:\.\d+)?)\)/);
|
|
85
|
+
if (m) return parseFloat(m[1]);
|
|
86
|
+
const n = parseFloat(v);
|
|
87
|
+
if (!isNaN(n)) return n;
|
|
33
88
|
}
|
|
34
|
-
return fallback
|
|
89
|
+
return fallback;
|
|
35
90
|
}
|
|
36
91
|
|
|
37
|
-
function rectToPath(
|
|
38
|
-
w: number,
|
|
39
|
-
h: number,
|
|
40
|
-
cr?: number | [number, number, number, number],
|
|
41
|
-
): string {
|
|
92
|
+
function rectToPath(w: number, h: number, cr?: number | [number, number, number, number]): string {
|
|
42
93
|
if (!cr || (typeof cr === 'number' && cr === 0)) {
|
|
43
|
-
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z
|
|
94
|
+
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
|
|
44
95
|
}
|
|
45
|
-
let [tl, tr, br, bl] =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
bl = Math.min(bl, maxR)
|
|
96
|
+
let [tl, tr, br, bl] = typeof cr === 'number' ? [cr, cr, cr, cr] : cr;
|
|
97
|
+
const maxR = Math.min(w, h) / 2;
|
|
98
|
+
tl = Math.min(tl, maxR);
|
|
99
|
+
tr = Math.min(tr, maxR);
|
|
100
|
+
br = Math.min(br, maxR);
|
|
101
|
+
bl = Math.min(bl, maxR);
|
|
52
102
|
return [
|
|
53
103
|
`M ${tl} 0`,
|
|
54
104
|
`L ${w - tr} 0`,
|
|
@@ -62,7 +112,7 @@ function rectToPath(
|
|
|
62
112
|
'Z',
|
|
63
113
|
]
|
|
64
114
|
.filter(Boolean)
|
|
65
|
-
.join(' ')
|
|
115
|
+
.join(' ');
|
|
66
116
|
}
|
|
67
117
|
|
|
68
118
|
function ellipseToPath(rx: number, ry: number): string {
|
|
@@ -74,32 +124,35 @@ function ellipseToPath(rx: number, ry: number): string {
|
|
|
74
124
|
`A ${rx} ${ry} 0 0 1 ${rx} 0`,
|
|
75
125
|
`A ${rx} ${ry} 0 0 1 ${rx * 2} ${ry}`,
|
|
76
126
|
'Z',
|
|
77
|
-
].join(' ')
|
|
127
|
+
].join(' ');
|
|
78
128
|
}
|
|
79
129
|
|
|
80
130
|
function polygonToPath(count: number, w: number, h: number): string {
|
|
81
|
-
const raw: [number, number][] = []
|
|
131
|
+
const raw: [number, number][] = [];
|
|
82
132
|
for (let i = 0; i < count; i++) {
|
|
83
|
-
const angle = (i * 2 * Math.PI) / count - Math.PI / 2
|
|
84
|
-
raw.push([Math.cos(angle), Math.sin(angle)])
|
|
133
|
+
const angle = (i * 2 * Math.PI) / count - Math.PI / 2;
|
|
134
|
+
raw.push([Math.cos(angle), Math.sin(angle)]);
|
|
85
135
|
}
|
|
86
|
-
let minX = Infinity,
|
|
136
|
+
let minX = Infinity,
|
|
137
|
+
maxX = -Infinity,
|
|
138
|
+
minY = Infinity,
|
|
139
|
+
maxY = -Infinity;
|
|
87
140
|
for (const [rx, ry] of raw) {
|
|
88
|
-
if (rx < minX) minX = rx
|
|
89
|
-
if (rx > maxX) maxX = rx
|
|
90
|
-
if (ry < minY) minY = ry
|
|
91
|
-
if (ry > maxY) maxY = ry
|
|
141
|
+
if (rx < minX) minX = rx;
|
|
142
|
+
if (rx > maxX) maxX = rx;
|
|
143
|
+
if (ry < minY) minY = ry;
|
|
144
|
+
if (ry > maxY) maxY = ry;
|
|
92
145
|
}
|
|
93
|
-
const rw = maxX - minX
|
|
94
|
-
const rh = maxY - minY
|
|
95
|
-
const parts: string[] = []
|
|
146
|
+
const rw = maxX - minX;
|
|
147
|
+
const rh = maxY - minY;
|
|
148
|
+
const parts: string[] = [];
|
|
96
149
|
for (let i = 0; i < count; i++) {
|
|
97
|
-
const px = ((raw[i][0] - minX) / rw) * w
|
|
98
|
-
const py = ((raw[i][1] - minY) / rh) * h
|
|
99
|
-
parts.push(i === 0 ? `M ${px} ${py}` : `L ${px} ${py}`)
|
|
150
|
+
const px = ((raw[i][0] - minX) / rw) * w;
|
|
151
|
+
const py = ((raw[i][1] - minY) / rh) * h;
|
|
152
|
+
parts.push(i === 0 ? `M ${px} ${py}` : `L ${px} ${py}`);
|
|
100
153
|
}
|
|
101
|
-
parts.push('Z')
|
|
102
|
-
return parts.join(' ')
|
|
154
|
+
parts.push('Z');
|
|
155
|
+
return parts.join(' ');
|
|
103
156
|
}
|
|
104
157
|
|
|
105
158
|
/** Convert a shape node to an SVG path `d` string in local coordinates (origin at 0,0). */
|
|
@@ -107,26 +160,26 @@ function nodeToLocalPath(node: PenNode): string | null {
|
|
|
107
160
|
switch (node.type) {
|
|
108
161
|
case 'rectangle':
|
|
109
162
|
case 'frame': {
|
|
110
|
-
const w = sizeVal(node.width, 100)
|
|
111
|
-
const h = sizeVal(node.height, 100)
|
|
112
|
-
return rectToPath(w, h, node.cornerRadius)
|
|
163
|
+
const w = sizeVal(node.width, 100);
|
|
164
|
+
const h = sizeVal(node.height, 100);
|
|
165
|
+
return rectToPath(w, h, node.cornerRadius);
|
|
113
166
|
}
|
|
114
167
|
case 'ellipse': {
|
|
115
|
-
const w = sizeVal(node.width, 100)
|
|
116
|
-
const h = sizeVal(node.height, 100)
|
|
117
|
-
return ellipseToPath(w / 2, h / 2)
|
|
168
|
+
const w = sizeVal(node.width, 100);
|
|
169
|
+
const h = sizeVal(node.height, 100);
|
|
170
|
+
return ellipseToPath(w / 2, h / 2);
|
|
118
171
|
}
|
|
119
172
|
case 'polygon': {
|
|
120
|
-
const w = sizeVal(node.width, 100)
|
|
121
|
-
const h = sizeVal(node.height, 100)
|
|
122
|
-
return polygonToPath(node.polygonCount || 6, w, h)
|
|
173
|
+
const w = sizeVal(node.width, 100);
|
|
174
|
+
const h = sizeVal(node.height, 100);
|
|
175
|
+
return polygonToPath(node.polygonCount || 6, w, h);
|
|
123
176
|
}
|
|
124
177
|
case 'path':
|
|
125
|
-
return node.d
|
|
178
|
+
return node.d;
|
|
126
179
|
case 'line':
|
|
127
|
-
return `M 0 0 L ${(node.x2 ?? (node.x ?? 0) + 100) - (node.x ?? 0)} ${(node.y2 ??
|
|
180
|
+
return `M 0 0 L ${(node.x2 ?? (node.x ?? 0) + 100) - (node.x ?? 0)} ${(node.y2 ?? node.y ?? 0) - (node.y ?? 0)}`;
|
|
128
181
|
default:
|
|
129
|
-
return null
|
|
182
|
+
return null;
|
|
130
183
|
}
|
|
131
184
|
}
|
|
132
185
|
|
|
@@ -135,109 +188,103 @@ function nodeToLocalPath(node: PenNode): string | null {
|
|
|
135
188
|
// ---------------------------------------------------------------------------
|
|
136
189
|
|
|
137
190
|
/** Types that can participate in boolean operations. */
|
|
138
|
-
const BOOLEAN_TYPES = new Set([
|
|
139
|
-
'rectangle',
|
|
140
|
-
'ellipse',
|
|
141
|
-
'polygon',
|
|
142
|
-
'path',
|
|
143
|
-
'line',
|
|
144
|
-
'frame',
|
|
145
|
-
])
|
|
191
|
+
const BOOLEAN_TYPES = new Set(['rectangle', 'ellipse', 'polygon', 'path', 'line', 'frame']);
|
|
146
192
|
|
|
147
193
|
export function canBooleanOp(nodes: PenNode[]): boolean {
|
|
148
|
-
if (nodes.length < 2) return false
|
|
149
|
-
return nodes.every((n) => BOOLEAN_TYPES.has(n.type))
|
|
194
|
+
if (nodes.length < 2) return false;
|
|
195
|
+
return nodes.every((n) => BOOLEAN_TYPES.has(n.type));
|
|
150
196
|
}
|
|
151
197
|
|
|
152
198
|
/**
|
|
153
199
|
* Create a Paper.js PathItem from a PenNode, positioned in absolute scene
|
|
154
200
|
* coordinates (applying x, y, rotation).
|
|
155
201
|
*/
|
|
156
|
-
function nodeToPaperPath(node: PenNode):
|
|
157
|
-
const d = nodeToLocalPath(node)
|
|
158
|
-
if (!d) return null
|
|
202
|
+
function nodeToPaperPath(node: PenNode): PaperPathItem | null {
|
|
203
|
+
const d = nodeToLocalPath(node);
|
|
204
|
+
if (!d) return null;
|
|
159
205
|
|
|
160
|
-
const s = getScope()
|
|
161
|
-
let item:
|
|
206
|
+
const s = getScope();
|
|
207
|
+
let item: PaperPathItem;
|
|
162
208
|
try {
|
|
163
|
-
item = s.CompoundPath.create(d)
|
|
209
|
+
item = s.CompoundPath.create(d);
|
|
164
210
|
} catch {
|
|
165
|
-
return null
|
|
211
|
+
return null;
|
|
166
212
|
}
|
|
167
213
|
|
|
168
214
|
// Apply node transform: translate to (x, y), then rotate around center
|
|
169
|
-
const x = node.x ?? 0
|
|
170
|
-
const y = node.y ?? 0
|
|
171
|
-
item.translate(new s.Point(x, y))
|
|
215
|
+
const x = node.x ?? 0;
|
|
216
|
+
const y = node.y ?? 0;
|
|
217
|
+
item.translate(new s.Point(x, y));
|
|
172
218
|
|
|
173
|
-
const rotation = node.rotation ?? 0
|
|
219
|
+
const rotation = node.rotation ?? 0;
|
|
174
220
|
if (rotation !== 0) {
|
|
175
221
|
// Rotate around the bounding-box center of the translated item
|
|
176
|
-
item.rotate(rotation, item.bounds.center)
|
|
222
|
+
item.rotate(rotation, item.bounds.center);
|
|
177
223
|
}
|
|
178
224
|
|
|
179
|
-
return item
|
|
225
|
+
return item;
|
|
180
226
|
}
|
|
181
227
|
|
|
182
228
|
/**
|
|
183
229
|
* Execute a boolean operation on the given PenNodes.
|
|
184
230
|
* Returns a new PathNode with the result, or null on failure.
|
|
185
231
|
*/
|
|
186
|
-
export function executeBooleanOp(
|
|
187
|
-
nodes
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
if (nodes.length < 2) return null
|
|
232
|
+
export function executeBooleanOp(nodes: PenNode[], operation: BooleanOpType): PathNode | null {
|
|
233
|
+
if (nodes.length < 2) return null;
|
|
234
|
+
|
|
235
|
+
if (!getPaperModule()) return null;
|
|
191
236
|
|
|
192
|
-
const paperPaths = nodes.map(nodeToPaperPath)
|
|
193
|
-
if (paperPaths.some((p) => p === null)) return null
|
|
237
|
+
const paperPaths = nodes.map(nodeToPaperPath);
|
|
238
|
+
if (paperPaths.some((p) => p === null)) return null;
|
|
194
239
|
|
|
195
|
-
const paths = paperPaths as
|
|
240
|
+
const paths = paperPaths as PaperPathItem[];
|
|
196
241
|
|
|
197
242
|
// Accumulate: fold left with the boolean operation
|
|
198
|
-
let result = paths[0]
|
|
243
|
+
let result = paths[0];
|
|
199
244
|
for (let i = 1; i < paths.length; i++) {
|
|
200
245
|
switch (operation) {
|
|
201
246
|
case 'union':
|
|
202
|
-
result = result.unite(paths[i])
|
|
203
|
-
break
|
|
247
|
+
result = result.unite(paths[i]);
|
|
248
|
+
break;
|
|
204
249
|
case 'subtract':
|
|
205
|
-
result = result.subtract(paths[i])
|
|
206
|
-
break
|
|
250
|
+
result = result.subtract(paths[i]);
|
|
251
|
+
break;
|
|
207
252
|
case 'intersect':
|
|
208
|
-
result = result.intersect(paths[i])
|
|
209
|
-
break
|
|
253
|
+
result = result.intersect(paths[i]);
|
|
254
|
+
break;
|
|
210
255
|
}
|
|
211
256
|
}
|
|
212
257
|
|
|
213
258
|
// Extract SVG path data
|
|
214
|
-
const pathData = result.pathData
|
|
215
|
-
if (!pathData || pathData.trim().length === 0) return null
|
|
259
|
+
const pathData = result.pathData;
|
|
260
|
+
if (!pathData || pathData.trim().length === 0) return null;
|
|
216
261
|
|
|
217
262
|
// Get bounding box for positioning
|
|
218
|
-
const bounds = result.bounds
|
|
263
|
+
const bounds = result.bounds;
|
|
219
264
|
|
|
220
265
|
// Translate path so it starts at origin (0,0)
|
|
221
|
-
|
|
222
|
-
|
|
266
|
+
const paper = getPaperModule();
|
|
267
|
+
if (!paper) return null;
|
|
268
|
+
result.translate(new paper.Point(-bounds.x, -bounds.y));
|
|
269
|
+
const originPathData = result.pathData;
|
|
223
270
|
|
|
224
271
|
// Clean up Paper.js items
|
|
225
|
-
for (const p of paths) p.remove()
|
|
226
|
-
result.remove()
|
|
272
|
+
for (const p of paths) p.remove();
|
|
273
|
+
result.remove();
|
|
227
274
|
|
|
228
275
|
// Build the label
|
|
229
276
|
const opLabels: Record<BooleanOpType, string> = {
|
|
230
277
|
union: 'Union',
|
|
231
278
|
subtract: 'Subtract',
|
|
232
279
|
intersect: 'Intersect',
|
|
233
|
-
}
|
|
280
|
+
};
|
|
234
281
|
|
|
235
282
|
// Inherit style from first operand
|
|
236
|
-
const first = nodes[0]
|
|
237
|
-
const fill = 'fill' in first ? first.fill : undefined
|
|
238
|
-
const stroke = 'stroke' in first ? first.stroke : undefined
|
|
239
|
-
const effects = 'effects' in first ? first.effects : undefined
|
|
240
|
-
const opacity = first.opacity
|
|
283
|
+
const first = nodes[0];
|
|
284
|
+
const fill = 'fill' in first ? first.fill : undefined;
|
|
285
|
+
const stroke = 'stroke' in first ? first.stroke : undefined;
|
|
286
|
+
const effects = 'effects' in first ? first.effects : undefined;
|
|
287
|
+
const opacity = first.opacity;
|
|
241
288
|
|
|
242
289
|
return {
|
|
243
290
|
id: nanoid(),
|
|
@@ -252,5 +299,5 @@ export function executeBooleanOp(
|
|
|
252
299
|
stroke,
|
|
253
300
|
effects,
|
|
254
301
|
opacity,
|
|
255
|
-
}
|
|
302
|
+
};
|
|
256
303
|
}
|
package/src/constants.ts
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
export const MIN_ZOOM = 0.02
|
|
2
|
-
export const MAX_ZOOM = 256
|
|
3
|
-
export const ZOOM_STEP = 0.1
|
|
4
|
-
export const SNAP_THRESHOLD = 5
|
|
5
|
-
export const DEFAULT_FILL = '#d1d5db'
|
|
6
|
-
export const DEFAULT_STROKE = '#374151'
|
|
7
|
-
export const DEFAULT_STROKE_WIDTH = 1
|
|
8
|
-
export const CANVAS_BACKGROUND_LIGHT = '#e5e5e5'
|
|
9
|
-
export const CANVAS_BACKGROUND_DARK = '#1a1a1a'
|
|
1
|
+
export const MIN_ZOOM = 0.02;
|
|
2
|
+
export const MAX_ZOOM = 256;
|
|
3
|
+
export const ZOOM_STEP = 0.1;
|
|
4
|
+
export const SNAP_THRESHOLD = 5;
|
|
5
|
+
export const DEFAULT_FILL = '#d1d5db';
|
|
6
|
+
export const DEFAULT_STROKE = '#374151';
|
|
7
|
+
export const DEFAULT_STROKE_WIDTH = 1;
|
|
8
|
+
export const CANVAS_BACKGROUND_LIGHT = '#e5e5e5';
|
|
9
|
+
export const CANVAS_BACKGROUND_DARK = '#1a1a1a';
|
|
10
10
|
|
|
11
|
-
export const SELECTION_BLUE = '#0d99ff'
|
|
12
|
-
export const COMPONENT_COLOR = '#a855f7'
|
|
13
|
-
export const INSTANCE_COLOR = '#9281f7'
|
|
11
|
+
export const SELECTION_BLUE = '#0d99ff';
|
|
12
|
+
export const COMPONENT_COLOR = '#a855f7';
|
|
13
|
+
export const INSTANCE_COLOR = '#9281f7';
|
|
14
14
|
|
|
15
15
|
// Hover / overlay / indicator
|
|
16
|
-
export const HOVER_BLUE = '#3b82f6'
|
|
17
|
-
export const HOVER_LINE_WIDTH = 1.5
|
|
18
|
-
export const HOVER_DASH = [4, 4]
|
|
19
|
-
export const INDICATOR_BLUE = '#3B82F6'
|
|
20
|
-
export const INDICATOR_LINE_WIDTH = 2
|
|
21
|
-
export const INDICATOR_DASH = [6, 4]
|
|
22
|
-
export const INDICATOR_ENDPOINT_RADIUS = 3
|
|
16
|
+
export const HOVER_BLUE = '#3b82f6';
|
|
17
|
+
export const HOVER_LINE_WIDTH = 1.5;
|
|
18
|
+
export const HOVER_DASH = [4, 4];
|
|
19
|
+
export const INDICATOR_BLUE = '#3B82F6';
|
|
20
|
+
export const INDICATOR_LINE_WIDTH = 2;
|
|
21
|
+
export const INDICATOR_DASH = [6, 4];
|
|
22
|
+
export const INDICATOR_ENDPOINT_RADIUS = 3;
|
|
23
23
|
|
|
24
24
|
// Frame labels
|
|
25
|
-
export const FRAME_LABEL_FONT_SIZE = 12
|
|
26
|
-
export const FRAME_LABEL_OFFSET_Y = 6
|
|
27
|
-
export const FRAME_LABEL_COLOR = '#999999'
|
|
25
|
+
export const FRAME_LABEL_FONT_SIZE = 12;
|
|
26
|
+
export const FRAME_LABEL_OFFSET_Y = 6;
|
|
27
|
+
export const FRAME_LABEL_COLOR = '#999999';
|
|
28
28
|
|
|
29
29
|
// Pen tool
|
|
30
|
-
export const PEN_ANCHOR_FILL = '#ffffff'
|
|
31
|
-
export const PEN_ANCHOR_RADIUS = 4
|
|
32
|
-
export const PEN_ANCHOR_FIRST_RADIUS = 5
|
|
33
|
-
export const PEN_HANDLE_DOT_RADIUS = 3
|
|
34
|
-
export const PEN_HANDLE_LINE_STROKE = '#888888'
|
|
35
|
-
export const PEN_RUBBER_BAND_STROKE = 'rgba(13, 153, 255, 0.5)'
|
|
36
|
-
export const PEN_RUBBER_BAND_DASH = [4, 4]
|
|
37
|
-
export const PEN_CLOSE_HIT_THRESHOLD = 8
|
|
30
|
+
export const PEN_ANCHOR_FILL = '#ffffff';
|
|
31
|
+
export const PEN_ANCHOR_RADIUS = 4;
|
|
32
|
+
export const PEN_ANCHOR_FIRST_RADIUS = 5;
|
|
33
|
+
export const PEN_HANDLE_DOT_RADIUS = 3;
|
|
34
|
+
export const PEN_HANDLE_LINE_STROKE = '#888888';
|
|
35
|
+
export const PEN_RUBBER_BAND_STROKE = 'rgba(13, 153, 255, 0.5)';
|
|
36
|
+
export const PEN_RUBBER_BAND_DASH = [4, 4];
|
|
37
|
+
export const PEN_CLOSE_HIT_THRESHOLD = 8;
|
|
38
38
|
|
|
39
39
|
// Dimension label
|
|
40
|
-
export const DIMENSION_LABEL_OFFSET_Y = 8
|
|
40
|
+
export const DIMENSION_LABEL_OFFSET_Y = 8;
|
|
41
41
|
|
|
42
42
|
// Default node colors
|
|
43
|
-
export const DEFAULT_FRAME_FILL = '#ffffff'
|
|
44
|
-
export const DEFAULT_TEXT_FILL = '#000000'
|
|
43
|
+
export const DEFAULT_FRAME_FILL = '#ffffff';
|
|
44
|
+
export const DEFAULT_TEXT_FILL = '#000000';
|
|
45
45
|
|
|
46
46
|
// Smart guides
|
|
47
|
-
export const GUIDE_COLOR = '#FF6B35'
|
|
48
|
-
export const GUIDE_LINE_WIDTH = 1
|
|
49
|
-
export const GUIDE_DASH = [3, 3]
|
|
47
|
+
export const GUIDE_COLOR = '#FF6B35';
|
|
48
|
+
export const GUIDE_LINE_WIDTH = 1;
|
|
49
|
+
export const GUIDE_DASH = [3, 3];
|