@zseven-w/pen-core 0.6.0 → 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 +109 -54
- 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 +131 -142
- 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 -224
- package/src/layout/normalize-tree.ts +140 -0
- package/src/layout/strip-redundant-section-fills.ts +155 -0
- package/src/layout/text-measure.ts +130 -106
- 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 +75 -81
- 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 +98 -106
package/src/boolean-ops.ts
CHANGED
|
@@ -1,76 +1,77 @@
|
|
|
1
|
-
import { nanoid } from 'nanoid'
|
|
2
|
-
import type { PenNode, PathNode } from '@zseven-w/pen-types'
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
2
|
+
import type { PenNode, PathNode } from '@zseven-w/pen-types';
|
|
3
3
|
|
|
4
|
-
export type BooleanOpType = 'union' | 'subtract' | 'intersect'
|
|
4
|
+
export type BooleanOpType = 'union' | 'subtract' | 'intersect';
|
|
5
5
|
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
// Paper.js scope — headless (no canvas needed)
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
|
|
10
10
|
interface PaperBoundsLike {
|
|
11
|
-
center: unknown
|
|
12
|
-
x: number
|
|
13
|
-
y: number
|
|
14
|
-
width: number
|
|
15
|
-
height: number
|
|
11
|
+
center: unknown;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
width: number;
|
|
15
|
+
height: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
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
|
|
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
27
|
}
|
|
28
28
|
|
|
29
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
|
|
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
34
|
CompoundPath: {
|
|
35
|
-
create: (pathData: string) => PaperPathItem
|
|
36
|
-
}
|
|
35
|
+
create: (pathData: string) => PaperPathItem;
|
|
36
|
+
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface PaperModule {
|
|
40
|
-
PaperScope: new () => PaperScope
|
|
41
|
-
Point: new (x: number, y: number) => unknown
|
|
40
|
+
PaperScope: new () => PaperScope;
|
|
41
|
+
Point: new (x: number, y: number) => unknown;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
let paperModule: PaperModule | null | undefined
|
|
45
|
-
let scope: PaperScope | null = null
|
|
44
|
+
let paperModule: PaperModule | null | undefined;
|
|
45
|
+
let scope: PaperScope | null = null;
|
|
46
46
|
|
|
47
47
|
function getPaperModule(): PaperModule | null {
|
|
48
|
-
if (paperModule !== undefined) return paperModule
|
|
48
|
+
if (paperModule !== undefined) return paperModule;
|
|
49
49
|
try {
|
|
50
50
|
// Indirect require via globalThis to load paper.js at runtime without
|
|
51
51
|
// triggering esbuild's direct-eval warning. The assignment to globalThis
|
|
52
52
|
// happens once; subsequent calls read from the cached paperModule.
|
|
53
|
-
const _r =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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;
|
|
57
58
|
} catch {
|
|
58
|
-
paperModule = null
|
|
59
|
+
paperModule = null;
|
|
59
60
|
}
|
|
60
|
-
return paperModule
|
|
61
|
+
return paperModule;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
function getScope(): PaperScope {
|
|
64
|
-
const paper = getPaperModule()
|
|
65
|
+
const paper = getPaperModule();
|
|
65
66
|
if (!paper) {
|
|
66
|
-
throw new Error('paper runtime is unavailable')
|
|
67
|
+
throw new Error('paper runtime is unavailable');
|
|
67
68
|
}
|
|
68
69
|
if (!scope) {
|
|
69
|
-
scope = new paper.PaperScope()
|
|
70
|
-
scope.setup(new scope.Size(1, 1))
|
|
70
|
+
scope = new paper.PaperScope();
|
|
71
|
+
scope.setup(new scope.Size(1, 1));
|
|
71
72
|
}
|
|
72
|
-
scope.activate()
|
|
73
|
-
return scope
|
|
73
|
+
scope.activate();
|
|
74
|
+
return scope;
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// ---------------------------------------------------------------------------
|
|
@@ -78,31 +79,26 @@ function getScope(): PaperScope {
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
|
|
80
81
|
function sizeVal(v: number | string | undefined, fallback: number): number {
|
|
81
|
-
if (typeof v === 'number') return v
|
|
82
|
+
if (typeof v === 'number') return v;
|
|
82
83
|
if (typeof v === 'string') {
|
|
83
|
-
const m = v.match(/\((\d+(?:\.\d+)?)\)/)
|
|
84
|
-
if (m) return parseFloat(m[1])
|
|
85
|
-
const n = parseFloat(v)
|
|
86
|
-
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;
|
|
87
88
|
}
|
|
88
|
-
return fallback
|
|
89
|
+
return fallback;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
function rectToPath(
|
|
92
|
-
w: number,
|
|
93
|
-
h: number,
|
|
94
|
-
cr?: number | [number, number, number, number],
|
|
95
|
-
): string {
|
|
92
|
+
function rectToPath(w: number, h: number, cr?: number | [number, number, number, number]): string {
|
|
96
93
|
if (!cr || (typeof cr === 'number' && cr === 0)) {
|
|
97
|
-
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`;
|
|
98
95
|
}
|
|
99
|
-
let [tl, tr, br, bl] =
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
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);
|
|
106
102
|
return [
|
|
107
103
|
`M ${tl} 0`,
|
|
108
104
|
`L ${w - tr} 0`,
|
|
@@ -116,7 +112,7 @@ function rectToPath(
|
|
|
116
112
|
'Z',
|
|
117
113
|
]
|
|
118
114
|
.filter(Boolean)
|
|
119
|
-
.join(' ')
|
|
115
|
+
.join(' ');
|
|
120
116
|
}
|
|
121
117
|
|
|
122
118
|
function ellipseToPath(rx: number, ry: number): string {
|
|
@@ -128,32 +124,35 @@ function ellipseToPath(rx: number, ry: number): string {
|
|
|
128
124
|
`A ${rx} ${ry} 0 0 1 ${rx} 0`,
|
|
129
125
|
`A ${rx} ${ry} 0 0 1 ${rx * 2} ${ry}`,
|
|
130
126
|
'Z',
|
|
131
|
-
].join(' ')
|
|
127
|
+
].join(' ');
|
|
132
128
|
}
|
|
133
129
|
|
|
134
130
|
function polygonToPath(count: number, w: number, h: number): string {
|
|
135
|
-
const raw: [number, number][] = []
|
|
131
|
+
const raw: [number, number][] = [];
|
|
136
132
|
for (let i = 0; i < count; i++) {
|
|
137
|
-
const angle = (i * 2 * Math.PI) / count - Math.PI / 2
|
|
138
|
-
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)]);
|
|
139
135
|
}
|
|
140
|
-
let minX = Infinity,
|
|
136
|
+
let minX = Infinity,
|
|
137
|
+
maxX = -Infinity,
|
|
138
|
+
minY = Infinity,
|
|
139
|
+
maxY = -Infinity;
|
|
141
140
|
for (const [rx, ry] of raw) {
|
|
142
|
-
if (rx < minX) minX = rx
|
|
143
|
-
if (rx > maxX) maxX = rx
|
|
144
|
-
if (ry < minY) minY = ry
|
|
145
|
-
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;
|
|
146
145
|
}
|
|
147
|
-
const rw = maxX - minX
|
|
148
|
-
const rh = maxY - minY
|
|
149
|
-
const parts: string[] = []
|
|
146
|
+
const rw = maxX - minX;
|
|
147
|
+
const rh = maxY - minY;
|
|
148
|
+
const parts: string[] = [];
|
|
150
149
|
for (let i = 0; i < count; i++) {
|
|
151
|
-
const px = ((raw[i][0] - minX) / rw) * w
|
|
152
|
-
const py = ((raw[i][1] - minY) / rh) * h
|
|
153
|
-
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}`);
|
|
154
153
|
}
|
|
155
|
-
parts.push('Z')
|
|
156
|
-
return parts.join(' ')
|
|
154
|
+
parts.push('Z');
|
|
155
|
+
return parts.join(' ');
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
/** Convert a shape node to an SVG path `d` string in local coordinates (origin at 0,0). */
|
|
@@ -161,26 +160,26 @@ function nodeToLocalPath(node: PenNode): string | null {
|
|
|
161
160
|
switch (node.type) {
|
|
162
161
|
case 'rectangle':
|
|
163
162
|
case 'frame': {
|
|
164
|
-
const w = sizeVal(node.width, 100)
|
|
165
|
-
const h = sizeVal(node.height, 100)
|
|
166
|
-
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);
|
|
167
166
|
}
|
|
168
167
|
case 'ellipse': {
|
|
169
|
-
const w = sizeVal(node.width, 100)
|
|
170
|
-
const h = sizeVal(node.height, 100)
|
|
171
|
-
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);
|
|
172
171
|
}
|
|
173
172
|
case 'polygon': {
|
|
174
|
-
const w = sizeVal(node.width, 100)
|
|
175
|
-
const h = sizeVal(node.height, 100)
|
|
176
|
-
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);
|
|
177
176
|
}
|
|
178
177
|
case 'path':
|
|
179
|
-
return node.d
|
|
178
|
+
return node.d;
|
|
180
179
|
case 'line':
|
|
181
|
-
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)}`;
|
|
182
181
|
default:
|
|
183
|
-
return null
|
|
182
|
+
return null;
|
|
184
183
|
}
|
|
185
184
|
}
|
|
186
185
|
|
|
@@ -189,18 +188,11 @@ function nodeToLocalPath(node: PenNode): string | null {
|
|
|
189
188
|
// ---------------------------------------------------------------------------
|
|
190
189
|
|
|
191
190
|
/** Types that can participate in boolean operations. */
|
|
192
|
-
const BOOLEAN_TYPES = new Set([
|
|
193
|
-
'rectangle',
|
|
194
|
-
'ellipse',
|
|
195
|
-
'polygon',
|
|
196
|
-
'path',
|
|
197
|
-
'line',
|
|
198
|
-
'frame',
|
|
199
|
-
])
|
|
191
|
+
const BOOLEAN_TYPES = new Set(['rectangle', 'ellipse', 'polygon', 'path', 'line', 'frame']);
|
|
200
192
|
|
|
201
193
|
export function canBooleanOp(nodes: PenNode[]): boolean {
|
|
202
|
-
if (nodes.length < 2) return false
|
|
203
|
-
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));
|
|
204
196
|
}
|
|
205
197
|
|
|
206
198
|
/**
|
|
@@ -208,94 +200,91 @@ export function canBooleanOp(nodes: PenNode[]): boolean {
|
|
|
208
200
|
* coordinates (applying x, y, rotation).
|
|
209
201
|
*/
|
|
210
202
|
function nodeToPaperPath(node: PenNode): PaperPathItem | null {
|
|
211
|
-
const d = nodeToLocalPath(node)
|
|
212
|
-
if (!d) return null
|
|
203
|
+
const d = nodeToLocalPath(node);
|
|
204
|
+
if (!d) return null;
|
|
213
205
|
|
|
214
|
-
const s = getScope()
|
|
215
|
-
let item: PaperPathItem
|
|
206
|
+
const s = getScope();
|
|
207
|
+
let item: PaperPathItem;
|
|
216
208
|
try {
|
|
217
|
-
item = s.CompoundPath.create(d)
|
|
209
|
+
item = s.CompoundPath.create(d);
|
|
218
210
|
} catch {
|
|
219
|
-
return null
|
|
211
|
+
return null;
|
|
220
212
|
}
|
|
221
213
|
|
|
222
214
|
// Apply node transform: translate to (x, y), then rotate around center
|
|
223
|
-
const x = node.x ?? 0
|
|
224
|
-
const y = node.y ?? 0
|
|
225
|
-
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));
|
|
226
218
|
|
|
227
|
-
const rotation = node.rotation ?? 0
|
|
219
|
+
const rotation = node.rotation ?? 0;
|
|
228
220
|
if (rotation !== 0) {
|
|
229
221
|
// Rotate around the bounding-box center of the translated item
|
|
230
|
-
item.rotate(rotation, item.bounds.center)
|
|
222
|
+
item.rotate(rotation, item.bounds.center);
|
|
231
223
|
}
|
|
232
224
|
|
|
233
|
-
return item
|
|
225
|
+
return item;
|
|
234
226
|
}
|
|
235
227
|
|
|
236
228
|
/**
|
|
237
229
|
* Execute a boolean operation on the given PenNodes.
|
|
238
230
|
* Returns a new PathNode with the result, or null on failure.
|
|
239
231
|
*/
|
|
240
|
-
export function executeBooleanOp(
|
|
241
|
-
nodes
|
|
242
|
-
operation: BooleanOpType,
|
|
243
|
-
): PathNode | null {
|
|
244
|
-
if (nodes.length < 2) return null
|
|
232
|
+
export function executeBooleanOp(nodes: PenNode[], operation: BooleanOpType): PathNode | null {
|
|
233
|
+
if (nodes.length < 2) return null;
|
|
245
234
|
|
|
246
|
-
if (!getPaperModule()) return null
|
|
235
|
+
if (!getPaperModule()) return null;
|
|
247
236
|
|
|
248
|
-
const paperPaths = nodes.map(nodeToPaperPath)
|
|
249
|
-
if (paperPaths.some((p) => p === null)) return null
|
|
237
|
+
const paperPaths = nodes.map(nodeToPaperPath);
|
|
238
|
+
if (paperPaths.some((p) => p === null)) return null;
|
|
250
239
|
|
|
251
|
-
const paths = paperPaths as PaperPathItem[]
|
|
240
|
+
const paths = paperPaths as PaperPathItem[];
|
|
252
241
|
|
|
253
242
|
// Accumulate: fold left with the boolean operation
|
|
254
|
-
let result = paths[0]
|
|
243
|
+
let result = paths[0];
|
|
255
244
|
for (let i = 1; i < paths.length; i++) {
|
|
256
245
|
switch (operation) {
|
|
257
246
|
case 'union':
|
|
258
|
-
result = result.unite(paths[i])
|
|
259
|
-
break
|
|
247
|
+
result = result.unite(paths[i]);
|
|
248
|
+
break;
|
|
260
249
|
case 'subtract':
|
|
261
|
-
result = result.subtract(paths[i])
|
|
262
|
-
break
|
|
250
|
+
result = result.subtract(paths[i]);
|
|
251
|
+
break;
|
|
263
252
|
case 'intersect':
|
|
264
|
-
result = result.intersect(paths[i])
|
|
265
|
-
break
|
|
253
|
+
result = result.intersect(paths[i]);
|
|
254
|
+
break;
|
|
266
255
|
}
|
|
267
256
|
}
|
|
268
257
|
|
|
269
258
|
// Extract SVG path data
|
|
270
|
-
const pathData = result.pathData
|
|
271
|
-
if (!pathData || pathData.trim().length === 0) return null
|
|
259
|
+
const pathData = result.pathData;
|
|
260
|
+
if (!pathData || pathData.trim().length === 0) return null;
|
|
272
261
|
|
|
273
262
|
// Get bounding box for positioning
|
|
274
|
-
const bounds = result.bounds
|
|
263
|
+
const bounds = result.bounds;
|
|
275
264
|
|
|
276
265
|
// Translate path so it starts at origin (0,0)
|
|
277
|
-
const paper = getPaperModule()
|
|
278
|
-
if (!paper) return null
|
|
279
|
-
result.translate(new paper.Point(-bounds.x, -bounds.y))
|
|
280
|
-
const originPathData = result.pathData
|
|
266
|
+
const paper = getPaperModule();
|
|
267
|
+
if (!paper) return null;
|
|
268
|
+
result.translate(new paper.Point(-bounds.x, -bounds.y));
|
|
269
|
+
const originPathData = result.pathData;
|
|
281
270
|
|
|
282
271
|
// Clean up Paper.js items
|
|
283
|
-
for (const p of paths) p.remove()
|
|
284
|
-
result.remove()
|
|
272
|
+
for (const p of paths) p.remove();
|
|
273
|
+
result.remove();
|
|
285
274
|
|
|
286
275
|
// Build the label
|
|
287
276
|
const opLabels: Record<BooleanOpType, string> = {
|
|
288
277
|
union: 'Union',
|
|
289
278
|
subtract: 'Subtract',
|
|
290
279
|
intersect: 'Intersect',
|
|
291
|
-
}
|
|
280
|
+
};
|
|
292
281
|
|
|
293
282
|
// Inherit style from first operand
|
|
294
|
-
const first = nodes[0]
|
|
295
|
-
const fill = 'fill' in first ? first.fill : undefined
|
|
296
|
-
const stroke = 'stroke' in first ? first.stroke : undefined
|
|
297
|
-
const effects = 'effects' in first ? first.effects : undefined
|
|
298
|
-
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;
|
|
299
288
|
|
|
300
289
|
return {
|
|
301
290
|
id: nanoid(),
|
|
@@ -310,5 +299,5 @@ export function executeBooleanOp(
|
|
|
310
299
|
stroke,
|
|
311
300
|
effects,
|
|
312
301
|
opacity,
|
|
313
|
-
}
|
|
302
|
+
};
|
|
314
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];
|